C++有关类设计异常处理的帮助

C++ Help on Class Design Exception Handling

本文关键字:异常处理 帮助 C++      更新时间:2023-10-16

我目前正在通过实现一个简单的地址簿应用程序来学习C++和实践我的知识。
我从一个Entry类和一个AddressBook类开始,它实现了STL Map,以按人员的姓氏访问条目。
现在我得到了以下代码:

Entry AddressBook::get_by_last_name(string last_name){
    if(this->addr_map.count(last_name) != 0){
        //What can I do here?
    } else {
        return addr_map[last_name];
    }

在脚本语言中,我只会返回类似-1, Error Message(Python 中的列表(的东西来指示函数失败。我不想抛出异常,因为它是应用程序逻辑的一部分。调用类应该能够通过在控制台上打印内容或打开消息框来响应请求。
现在我想到了通过在类Entry中引入某种无效状态来实现C++中的脚本语言方法。但这在C++不是不好的做法吗?会不会是我的全班设计不合适?我感谢任何帮助。请记住,我还在学习C++。

关于代码的一些快速说明:

if(this->addr_map.count(last_name) != 0){
    //What can I do here?

您可能希望以另一种方式:

if(this->addr_map.count(last_name) == 0){
    //handle error

但你真正的问题就在这里:

return addr_map[last_name];

这里需要注意两件事:

  1. map 的operator[]可以做两件事:如果元素存在,则返回它;如果该元素不存在,它将创建一个新的(键,值(pair,具有指定的键和值的默认constructor。可能不是你想要的。但是,如果您之前的if陈述是正确的方式,那么后者永远不会发生,因为我们事先知道密钥存在。
    1. 在之前调用count()时,您可以有效地告诉map尝试查找元素。通过调用operator[],您是在告诉map再次找到它。因此,检索单个值需要执行两倍的工作。

一个更好(更快(的方法涉及迭代器和find方法:

YourMap::iterator it = addr_map.find(last_name); //find the element (once)
if (it == addr_map.end()) //element not found
{
    //handle error
}
return *it.second; //return element

现在,回到手头的问题。找不到last_name怎么办?正如其他答案所指出的:

  • 最简单的解决方案是返回一个指针(如果未找到,则为 NULL(
  • 使用 boost::optional .
  • 只需返回YourMap::iterator,但似乎您正在尝试对AddressBook用户"隐藏"map,因此这可能是一个坏主意。
  • throw exception.但是等等,现在您必须首先检查调用此方法是否"安全"(或在适当时处理exception(。此检查需要一个布尔方法,如 lastNameExists 在调用 get_by_last_name 之前必须调用该方法。当然,我们又回到了方格1。我们正在执行 2 个查找操作来检索单个值。这是安全的,但是如果您正在对get_by_last_name进行大量调用,那么这可能是使用不同解决方案进行优化的好地方(此外,可以说例外不是很有建设性:搜索不存在的东西有什么问题,嗯?
  • Entry创建一个dummy成员,表明这不是一个真正的Entry,但这是非常糟糕的设计(难以管理,违反直觉,浪费 - 你的名字(。

如您所见,前 2 种解决方案是更可取的。

一个非常简单的选项是将返回类型更改为 Entry*(或 const Entry*(,然后返回条目的地址(如果找到(或 NULL(如果未找到(。

如果你使用Boost,你可以返回一个boost::optional<Entry>,在这种情况下,你的成功代码将是相同的,但在未找到时,你会说return boost::none。 这更漂亮,但与使用指针返回类型大致相同。

抛出异常绝对是"正确"C++做的事情,具体取决于您的函数返回类型。

不过,您可能需要这样的函数来帮助您:

bool AddressBook::lastNameExists(const string &last_name)
{
    return addr_map.count(last_name) > 0;
}

请注意,当前代码返回条目"按值",因此修改返回的条目不会更新映射。不确定这是偶然还是设计...

其他答案给出了各种方法,其中大多数是有效的。我还没有看到这个:

您可以添加具有默认值的第二个参数:

Entry AddressBook::get_by_last_name(string last_name, const Entry& default_value){
    if(this->addr_map.count(last_name) == 0){
        return default_value; 
    } else {
        return addr_map[last_name];
    }

在此特定实例中,不存在的姓氏可能没有合理的默认值,但在许多情况下存在。

在C++中,您可以通过多种方式发出函数中出现问题的信号。

您可以返回一个特殊值,调用代码会将该值识别为无效值。如果函数应返回指针,这可以是NULL指针,或者如果函数返回数组中的索引,则可以是负值,或者在自定义类(例如Entry类(的情况下,您可以定义一个特殊的Entry::invalid值或调用函数可以检测到的类似值。

您的调用代码可能如下所示

if ( entryInstance->get_by_last_name("foobar") != Entry::invalid) 
{
  // here goes the code for the case where the name is valid
} else {
  // here goes the code for the case where the name is invalid
}

另一方面,您可以使用C++异常机制并使函数引发异常。为此,您可以创建自己的异常类(或使用标准库中定义的异常类,从 std::exception 派生(。您的函数将throw异常,您的调用代码必须通过try捕获它......catch声明。

try
  {
    entryInstance->get_by_last_name("foobar")
  }
  catch (Exception e)
  {
    // here goes the code for the case where the name is invalid
  }
  // here goes the code for the case where the name is valid

除了每个姓氏可以有多个条目的事实。

消除getter,你已经解决了问题,或者至少把它转移到其他地方。

告诉AddressBook显示有姓氏的人。如果没有,它什么也做不了。

AddressBookRenderer renderer;
AddressBook contacts;
contacts.renderSurnames("smith", renderer);
contacts.renderCompletions("sm", renderer);
//etc

你可以做std::map(和其他容器所做的(。

从搜索函数返回迭代器。
如果搜索没有找到有用的值,则返回迭代器 to end((。

class AddressBook
{
        typedef  <Your Container Type>  Container;
    public:
        typedef  Container::iterator   iterator;

        iterator get_by_last_name(std::string const& lastName) {return addr_map.find[lastName];}
        iterator end()                                         {return addr_map.end();}
};

您的地址簿是一个类似容器的对象。
在搜索中找不到项目可能会发生,但它没有足够的上下文来合并错误处理代码(因为地址簿可以从很多地方使用,每个地方都有不同的错误处理想法(。

因此,您必须将"未找到"状态的测试移出通讯簿。
就像"Python"一样,我们返回一个标记。在C++中,这通常是 end(( 的迭代器,调用代码可以检查并采取适当的操作。

 AddressBook&  ab = getAddressBookRef();
 AddressBook::iterator find = ab.get_by_last_name("cpp_hobbyist");
 if (find != ab.end())
 {
     Entity&  person *find;  // Here you have a reference to your entity.
     // you can now manipulate as you want.
 }
 else
 {
     // Display appropriate error message
 }