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
。这很有趣,但与使用指针返回类型大致相同。
在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;
}
注意,目前的代码返回“按价值”,所以修改返回的条目不会更新地图的条目。不知道这是偶然还是设计...
但现在我们搜索两次地址簿。一次来测试它是否存在并且一次获得价值。 – 2013-02-18 15:14:45
@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];
两件事情来请注意:
- 地图的
operator[]
可以做2件事:如果元素存在,则返回它;如果元素不存在,则创建一个带有指定键和值的默认constructor
的新(键值)pair
。可能不是你想要的。但是,如果您之前的陈述是正确的,那么后者将永远不会发生,因为我们知道关键在于事先存在。- 之前在致电
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
anexception
。但是请等待,现在您必须首先检查是否调用此方法是“安全”的(或者在适当时处理exception
)。此检查需要一个布尔方法,如调用get_by_last_name
之前必须调用的lastNameExists
。当然,然后我们回到方块1.我们执行2次查找操作来检索单个值。这是安全的,但如果你正在做很多get_by_last_name
的调用,那么这可能是一个用不同的解决方案进行优化的好地方(另外,可以说这个例外并不是非常有建设性的:寻找不存在的东西有什么问题,嗯?)。 - 为
Entry
表明创建dummy
件不是一个真正的Entry
但这是设计非常差(难以管理,直觉,浪费 - 你的名字)。
正如你所看到的,前两种解决方案是最好的。
除了每个姓氏可以有多个条目的事实。
消除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];
}
在这种特定情况下,有可能不是一个非明智的默认值现有的姓氏,但在很多情况下都有。
注意:'default_value'应该是Entry类型。 – 2013-02-18 15:15:32
@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
}
将提升::可选更改我返回的类型的大小?我的意思是它必须在任何地方保存额外的信息? – 2013-02-18 12:12:17
如果使用的是相当近的编译器,则使用'nullptr'而不是'NULL'。这是一个c + + 11关键字,正是一个指针是0 http://stackoverflow.com/questions/1282295/what-exactly-is-nullptr – 2013-02-18 12:33:37