C++有关类设计异常处理的帮助
C++ Help on Class Design Exception Handling
我目前正在通过实现一个简单的地址簿应用程序来学习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];
这里需要注意两件事:
- map 的
operator[]
可以做两件事:如果元素存在,则返回它;如果该元素不存在,它将创建一个新的(键,值(pair
,具有指定的键和值的默认constructor
。可能不是你想要的。但是,如果您之前的if
陈述是正确的方式,那么后者永远不会发生,因为我们事先知道密钥存在。- 在之前调用
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
}
- 为什么我应该在异常处理中使用std::cerr而不是std::cout
- 当我使用 C++ 中的 C# dll 来使用 Selenium 时,存在异常处理问题
- Firebase C++VS2018 SDL2-在Firebase::app::create(..)上执行异常处理
- 使用 stoi 功能进行异常处理
- 子系统中的异常处理:本机
- 与异常处理程序中的操作员<<不匹配
- 数组 C++ 上的异常处理程序
- 异常处理:如果用户输入不是三个特定字符之一
- C++ 异常处理错误输出
- 视觉 std::矢量无异常:警告 C4530:使用了C++异常处理程序,但未启用展开语义.指定 /EHsc
- C++交换机状态异常处理
- 在字符串类上的成员函数和out_of_range异常处理
- 奇怪的消息 (_Base_bitset::_M_do_to_ulong) 从溢出异常处理程序中打印出来
- 执行视觉工作室异常处理模式
- 为什么隐式转换在异常处理中从派生到基?
- C++执行期间的类成员函数错误/异常处理
- C++ 中未处理的异常处理程序
- 用户定义的异常处理
- C :ScopeGuard vs返回支票和异常处理
- C++有关类设计异常处理的帮助