我使用C ++ catch子句,异常类的族和破坏是否合理?

时间:2010-12-29 07:53:35

标签: c++ exception-handling

有一段时间,我注意到我已经使用多年的编码模式,这让我很紧张。我没有具体的问题,但我也不记得为什么我采用了这种模式,并且它的某些方面似乎与某些反模式匹配。这最近发生在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块可以完成这项工作。

我仍然感到紧张,因为在一个或两个编译器中进行的测试不能证明标准所说的内容,而且我的经验表明,我认为常识通常与语言所保证的不同。

那么 - 这种处理异常的模式(使用引用类型捕获它们)是否安全?或者我应该做些别的事情,比如......

  • 捕获(并显式删除)指向堆分配实例的指针,而不是引用看起来(当抛出时)非常像临时的东西?
  • 使用智能指针类?
  • 使用“pass-by-value”catch子句,并接受我无法从具有一个catch子句的层次结构中捕获任何异常类?
  • 我没想过的东西?

5 个答案:

答案 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 ++编码标准'中。