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

问题描述:

我目前在学习C++,并通过实现一个简单的AddressBook应用程序来练习我的知识。
我从一个Entry类和一个AddressBook类开始,它实现了一个STL Map来按照人的姓氏访问条目。
现在我来到了下面的代码:C++关于类设计异常处理的帮助

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++。

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

如果您使用Boost,则可以返回boost::optional<Entry>,在这种情况下,您的成功代码将相同,但在未找到时您会说return boost::none。这很有趣,但与使用指针返回类型大致相同。

+0

将提升::可选更改我返回的类型的大小?我的意思是它必须在任何地方保存额外的信息? – 2013-02-18 12:12:17

+0

如果使用的是相当近的编译器,则使用'nullptr'而不是'NULL'。这是一个c + + 11关键字,正是一个指针是0 http://*.com/questions/1282295/what-exactly-is-nullptr – 2013-02-18 12:33:37

在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 

根据你的函数返回类型,抛出一个异常肯定是'正确'的C++事情。

您可能需要这样的功能来帮助你,但:

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

注意,目前的代码返回“按价值”,所以修改返回的条目不会更新地图的条目。不知道这是偶然还是设计...

+0

但现在我们搜索两次地址簿。一次来测试它是否存在并且一次获得价值。 – 2013-02-18 15:14:45

+0

@LokiAstari - 在某些情况下,你可能会同意。这并不完美。我真正的观点是,经常尝试访问不存在的映射条目是程序逻辑错误(而不是用户验证错误)。 – Roddy 2013-02-18 15:45:25

你的代码的一些快速注解:

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. 地图的operator[]可以做2件事:如果元素存在,则返回它;如果元素不存在,则创建一个带有指定键和值的默认constructor的新(键值)pair。可能不是你想要的。但是,如果您之前的陈述是正确的,那么后者将永远不会发生,因为我们知道关键在于事先存在。
    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用户“隐藏”,所以这可能是一个坏主意。
  • throw an exception。但是请等待,现在您必须首先检查是否调用此方法是“安全”的(或者在适当时处理exception)。此检查需要一个布尔方法,如调用get_by_last_name之前必须调用的lastNameExists。当然,然后我们回到方块1.我们执行2次查找操作来检索单个值。这是安全的,但如果你正在做很多get_by_last_name的调用,那么这可能是一个用不同的解决方案进行优化的好地方(另外,可以说这个例外并不是非常有建设性的:寻找不存在的东西有什么问题,嗯?)。
  • Entry表明创建dummy不是一个真正的Entry但这是设计非常差(难以管理,直觉,浪费 - 你的名字)。

正如你所看到的,前两种解决方案是最好的。

+1

不错,但是对于“每次调用get_by_last_name都需要用try ... catch块进行包装”非常错误。你会在'catch'块中做什么 - 返回一个错误代码?引发异常?有些情况下,如果无法找到该元素,您可能需要'默默'做某些事情,在这种情况下,'lastNameExists'方法可能是一个有用的补充。 – Roddy 2013-02-18 14:06:45

+0

参见上次编辑,它反映了对try ... catch方法的更仔细研究 – eladidan 2013-02-18 14:21:07

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

消除getter,你已经解决了这个问题,或者至少把它转移到别处了。

告诉AddressBook显示给定姓氏的人。如果没有,它什么都不能做。

AddressBookRenderer renderer; 
AddressBook contacts; 

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

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

你可以用默认值添加第二个参数:

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]; 
    } 

在这种特定情况下,有可能不是一个非明智的默认值现有的姓氏,但在很多情况下都有。

+0

注意:'default_value'应该是Entry类型。 – 2013-02-18 15:15:32

+0

@LokiAstari你说得对,谢谢! – Sjoerd 2013-02-18 19:43:11

你可以做什么std :: map(和其他容器做的)。

您从搜索功能中返回一个迭代器。
如果搜索未找到有用的值,则返回一个迭代器以结束()。

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 
}