pas*_*ugh 4 c++ exception std hierarchy option-type
如果在没有初始化实际值时调用 的成员函数std::optional,则会抛出 a 。由于它直接派生自,因此您需要或来处理异常。然而,这两种选择对我来说似乎都很悲伤:value()optionalstd::bad_optional_accessstd::exceptioncatch (std::bad_optional_access const&)catch (std::exception const&)
std::exception捕获每一个异常std::bad_optional_access公开实施细节。考虑以下示例:Placement Item::get_placement() const {
// throws if the item cannot be equipped
return this->placement_optional.value();
}
void Unit::equip_item(Item acquisition) {
// lets the exception go further if it occurs
this->body[acquisition.get_placement()] = acquisition;
}
Run Code Online (Sandbox Code Playgroud)
// somewhere far away:
try {
unit.equip_item(item);
} catch (std::bad_optional_access const& exception) { // what is this??
inform_client(exception.what());
}
Run Code Online (Sandbox Code Playgroud)
std::optional因此,要捕获异常,您需要充分了解 的实现中的用法Item,从而获得已知问题的列表。我都不想捕获并重新包装,std::bad_optional_access因为(对我来说)异常的关键部分是在需要之前忽略它们的可能性。这就是我认为正确的方法:
std::exception
<- std::logic_error
<- std::wrong_state (doesn't really exist)
<- std::bad_optional_access (isn't really here)
Run Code Online (Sandbox Code Playgroud)
因此,“遥远”的例子可以写成这样:
try {
unit.equip_item(item);
} catch (std::wrong_state const& exception) { // no implementation details
inform_client(exception.what());
}
Run Code Online (Sandbox Code Playgroud)
最后,
std::bad_optional_access设计成这样?注:boost::bad_optional_access源自std::logic_error. 好的!
注2:我知道catch (...)并抛出家庭以外类型的物体std::exception。为了简洁(和理智),它们被省略。
更新:不幸的是,我不能接受两个答案,所以:如果你对这个话题感兴趣,你可以阅读胡作典的回答和他们的评论。
因此,要捕获异常,您需要充分了解
std::optionalItem 实现中的用法
不,要捕获异常,您必须阅读 的文档get_placement,它会告诉您它会抛出std::bad_optional_access。通过选择发出该异常,该函数使该异常的发出成为该函数接口的一部分。
因此,它并不Item比直接返回 a 更依赖于 的实现std::optional。您选择将其放入界面中,因此您应该承担后果。
换句话说,如果您觉得std::optional作为参数类型或返回值放置是错误的,那么直接发射也应该有同样的感觉bad_optional_exception。
最终,这一切都回到了错误处理的最基本问题之一:在错误的具体性质变得实际上毫无意义甚至完全不同之前,您可以距离错误站点多远?
假设您正在进行文本处理。您有一个文件,每行包含 3 个浮点数。您正在逐行处理它,并将每组三个值插入到列表中。并且您有一个将字符串转换为浮点数的函数,如果转换失败,该函数将引发异常。
所以代码大致如下:
for each line
split the line into a 3-element list of number strings.
for each number string
convert the string into a number.
add the number to the current element.
push the element into the list.
Run Code Online (Sandbox Code Playgroud)
好吧,那么...如果您的字符串到浮点转换器抛出异常,会发生什么?那要看; 你想要什么发生什么?这取决于谁能抓住它。如果您想要错误的默认值,那么最内层循环中的代码会捕获它并将默认值写入元素。
但也许您想记录特定行有错误,然后跳过该行(不要将其添加到列表中),但继续正常处理文本的其余部分。在这种情况下,您可以在第一个循环中捕获异常。
到那时,错误的含义就发生了变化。引发的错误是“此字符串不包含有效的浮点数”,但这不是您的代码处理它的方式。事实上,捕获代码已经完全丢失了错误的上下文。它不知道是文本中的第一个、第二个还是第三个字符串导致了失败。最好的情况是,它知道它位于该行的某个位置,并且异常可能恰好包含几个指向错误字符串范围的指针(尽管异常距离其源越远,由于可能存在悬空指针,这种情况就越危险) )。
如果转换失败意味着整个过程不再可信,您正在构建的列表无效并且应该被丢弃怎么办?这比前一个案例的来龙去脉更加少,意义也更加混乱和遥远。此时,错误仅意味着终止列表构建过程。也许您整理了一个日志条目,但这就是您此时要做的全部事情。
距离抛出异常的位置越远,丢失的有关错误的上下文就越多,并且含义最终偏离错误的初始含义就越多。这不仅仅是一个实施细节;它与信息的位置以及对该信息的响应有关。
所以基本上,接近错误源的代码正在捕获具有上下文含义的特定异常。捕获距错误源越远,捕获代码就越有可能变得非常通用,处理模糊的“由于原因而不起作用”之类的事情。这就是模糊类型的用武之地std::logic_error。
事实上,人们可以想象,在该过程的每一步,异常都会被重新解释(“重新解释”,我的意思是通过将其转换为不同的类型catch/throw)。字符串到浮点转换器抛出一个有意义的异常:无法将字符串转换为浮点。尝试从 3 个字符串构建元素的层将异常转换为对其调用者有价值的内容:字符串索引 X 格式错误。在最后一个阶段,异常被概括为:由于 Y 行而无法解析列表。
单个异常类型可以跳过整个代码库和设计意图并仍然保留其初始含义的想法是一个幻想。当异常必须传递中性代码时,例如从回调中抛出异常或间接函数执行的某些其他情况,异常会非常有效。在这种情况下,引发执行的代码仍然具有引发异常的进程的本地上下文。但是,距离了解发生了什么情况的本地环境越远,特定例外的意义就越小。
logic_error由于这些原因,继承自是错误的。捕捉 abad_optional_access最终是一件非常本地化的事情。过了某个点,该错误的含义就会发生变化。
“逻辑错误”代表你的程序无法理解。但是不包含值的可选值并不一定代表这样的问题。在一段代码中,有一个空的可选值可能是完全有效的,并且抛出的异常只是如何将其报告给调用者。另一段代码可能会在某个时刻将可选值视为空,因为用户在其 API 使用中犯了一些先前的错误。其中一个是逻辑错误,另一个则不是。
最终,正确的做法是确保您的类 API 都发出对调用者有意义的异常。目前还不清楚 的bad_optional_access调用者意味着什么get_placement。
您说异常的主要吸引力在于您可以在调用堆栈的尽可能深处忽略它们。据推测,考虑到您希望避免泄漏实现细节,您不能再让异常传播超出其处理程序无法理解和修复的范围。这似乎与您的理想设计相矛盾:它致力于向用户修复异常,但是bad_optional_access::what对于刚刚发生的情况却没有上下文\xe2\x80\x93向用户泄露实现细节。当用户看到的最多只是“无法装备物品:bad_Optional_access”时,您如何期望用户针对装备失败采取有意义的操作?
显然你已经过度简化了,但挑战仍然存在。即使有“更好”的异常层次结构,std::bad_optional_access也没有足够的上下文,以至于除了非常接近的调用者之外的任何东西都可能知道如何处理它。
您可能想要抛出几种截然不同的情况:
\n\nif块。当您遇到感觉不正确的异常问题时,通常是因为您正在尝试处理一个错误,该错误的级别与您希望的操作级别不同。希望更改异常层次结构只是试图使该异常符合您的特定用途,这会导致与其他人如何使用它的紧张关系。
\n\n显然,C++ 委员会认为它bad_optional_access属于第一类,而你问为什么它不属于第三类。我认为您应该翻转问题并问自己要捕获异常的目的是什么,而不是尝试忽略异常直到您“需要”对它们执行某些操作。
如果答案确实是“用户”,那么您应该抛出一些不是 a 的东西,bad_optional_access而是具有高级功能的东西,例如本地化错误消息和足够的数据,能够inform_user打开一个具有有意义标题的对话框、主文本、副文本、按钮、图标等。
如果答案是这是一般游戏引擎错误并且可能在游戏的常规过程中发生,那么您应该抛出一些内容,表明装备该物品失败,而不是存在状态错误。与无法描述的状态错误相比,您更有可能从装备失败中恢复,包括如果您需要为用户生成一个漂亮的错误。
\n\n如果答案是您可能会尝试连续装备 25 件物品,并且希望在出现问题时立即停止,那么您无需对 进行任何更改bad_optional_access。
另请注意,不同的实现使不同的使用或多或少变得方便。在大多数现代 C++ 实现中,不抛出异常的代码路径没有性能开销,而抛出异常的路径则有巨大的开销。这通常会阻碍对第一类错误使用异常。
\n| 归档时间: |
|
| 查看次数: |
10720 次 |
| 最近记录: |