有一段时间,我注意到我已经使用多年的编码模式,这让我很紧张。我没有具体的问题,但我也不记得为什么我采用了这种模式,并且它的某些方面似乎与某些反模式匹配。这最近发生在WRT我的一些代码如何使用异常。
令人担忧的事情涉及我通过“引用”捕获异常的情况,以类似于将参数处理为函数的方式对待它。这样做的一个原因是我可以拥有异常类的继承层次结构,并根据应用程序指定更通用或更精确的捕获类型。例如,我可能会定义......
class widget_error {};
class widget_error_all_wibbly : public widget_error {};
class widget_error_all_wobbly : public widget_error {};
void wibbly_widget ()
{
throw widget_error_all_wibbly ();
}
void wobbly_widget ()
{
throw widget_error_all_wobbly ();
}
void call_unknown_widget (void (*p_widget) ())
{
try
{
p_widget ();
}
catch (const widget_error &p_exception)
{
// Catches either widget_error_all_wibbly or
// widget_error_all_wobbly, or a plain widget_error if that
// is ever thrown by anything.
}
}
现在这让我很担心,因为我注意到在一个函数中构造了一个类实例(作为throw的一部分),但在该函数退出后被引用(通过p_Exception catch-clause“参数”)。这通常是一个反模式 - 一个局部变量的引用或指针或在函数内创建的临时变量,但在函数退出时传递出来,通常是一个悬空引用/指针,因为局部变量/ temporary被破坏并且内存被释放当函数退出时。
一些快速测试表明上面的抛出可能没问题 - 当函数退出时,throw子句中构造的实例不会被破坏,但是当处理它的catch子句完成时会被破坏 - 除非catch块重新抛出异常,在这种情况下,下一个catch块可以完成这项工作。
我仍然感到紧张,因为在一个或两个编译器中进行的测试不能证明标准所说的内容,而且我的经验表明,我认为常识通常与语言所保证的不同。
那么 - 这种处理异常的模式(使用引用类型捕获它们)是否安全?或者我应该做些别的事情,比如......
答案 0 :(得分:9)
这没关系。通过常量引用捕获异常实际上很好(并且很难捕获指针)。按值捕获会产生不必要的副本。编译器足够聪明,可以正确处理异常(及其破坏) - 只是不要尝试在catch块之外使用异常引用; - )
实际上,我经常做的是从std :: runtime_error(继承自std :: exception)继承我的层次结构。然后我可以使用.what()
,并在处理更多异常时使用更少的catch块。
答案 1 :(得分:6)
这种模式绝对安全。
有一些特殊规则可以延长抛出对象的生命周期。实际上,只要它被处理它就存在,并且它保证存在直到最后一个处理它的catch
块结束。
一个非常常见的习惯用法,例如,从std::exception
派生自定义异常,覆盖其what()
成员函数,并通过引用捕获它,以便您可以从各种异常中打印错误消息一个捕获条款。
答案 2 :(得分:4)
不,你肯定是在做对了。有关此事,请参阅http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.13以及常见问题解答章节的其余部分。
答案 3 :(得分:4)
是。到目前为止一切都很好。
我个人使用std :: runtime_error作为所有异常calsses的基础。它处理错误消息等。
也不要声明您需要的更多例外。仅为可以实际捕获和修复的事物定义例外。对于无法捕获或修复的事物,请使用更通用的例外。
例如:如果我开发了一个库A.那么我将从std :: runtime_error派生一个AException。此异常将用于库中的所有常规异常。对于任何特定的异常,其中库的用户实际上可以捕获并执行某些操作(修复或缓解),然后我将创建一个从AException派生的特定异常(但只有在可以通过异常执行某些操作时)。
答案 4 :(得分:4)
确实,Sutter and Alexandrescu recomend this pattern在他们的'C ++编码标准'中。