【问题标题】:Shall I place try...catch block in destructor, if I know my function won't throw exception如果我知道我的函数不会抛出异常,我应该将 try...catch 块放在析构函数中吗
【发布时间】:2010-12-23 22:31:07
【问题描述】:

我知道析构函数不应该抛出异常。

http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.13

我有以下代码:

~a()
{
    cleanup();
}

// I do not expect exception being thrown in this function.
// If exception really happen, I know that it is something not recoverable.
void a::cleaup()
{
    delete p;
}

在我的静态源代码分析中,它抱怨我应该这样调用清理函数:

~a()
{
    try {
        cleanup();
    }
    catch(...) {
        // What to do? Print out some logging message?
    }
}

// I do not expect exception being thrown in this function.
// If exception really happen, I know that it is something not recoverable.
void a::cleaup()
{
    delete p;
}

我不确定这是否是一个好习惯,只要它调用函数,就将 try...catch 块放在析构函数中。如:

(1) 如果清理函数能够抛出异常,我就知道发生了不好的事情。我更喜欢它是fail-fast。也就是让整个系统崩溃,让程序员去调试。

(2) 进入和退出 try...catch 块时发生开销。

(3) 代码看起来很繁琐,在类的析构函数周围有很多 try...catch 块。

我可能会错过一些其他的点,为什么 try...catch 块应该到位。

谢谢。

【问题讨论】:

    标签: c++ exception-handling destructor


    【解决方案1】:

    由于delete 不会抛出,cleanup 也不会抛出,因此无需将调用置于 try-catch 中。

    由于您的静态分析工具可能很难弄清楚这一点,也许您可​​以通过将 cleanup 声明为 no-throw 来帮助它(虽然这只是一个猜测)。

    void cleanup() throw();
    

    【讨论】:

    • +1,但要确保 p 的析构函数确实是 nothrow。如果在展开 A 时异常离开 ~a,由于调用 unexpected() 而摔倒并没有比由于 terminate() 摔倒更好或更糟。但是静态分析工具大概认为是……
    【解决方案2】:

    也许您可以更准确地了解您的清理功能。 cleanup() 函数是否会故意抛出异常,即使只是在极少数情况下?那么你绝对应该在你的析构函数中处理这种情况,因为即使很少见,异常抛出也是 cleanup() 的预期行为。

    至少,您的异常处理代码可以让您的程序处于明确定义的状态,从而让您能够优雅地结束您的程序。

    或者你的意思是 cleanup() 可能会抛出例如OutOfMemoryException 或您从未在其他任何地方处理过的另一个运行时异常?然后我也会省略析构函数中的异常处理。在这种不寻常的情况下,您的应用程序可能无论如何都无法运行其错误处理代码。您应该尽一切可能确保即使是这样的异常也能被正确报告或记录。

    编辑:

    现在您已经展示了cleanup 的实现,Neil 已经回答了您的问题。 delete p 不能抛出,因此只要p 的析构函数没有抛出,a::cleanup 就不会抛出。

    【讨论】:

      【解决方案3】:

      问题确实出在 p 的析构函数中。当你在 cleanup() 中说

      delete p;
      

      delete 本身不能抛出,因此异常必须来自 p 的析构函数。正是在那个析构函数中,您需要担心使用 try 块,而不是 a 块。

      【讨论】:

        【解决方案4】:

        由于内存分配或删除而引发错误通常没有任何意义。它要么工作要么不工作,你可以花费大量的工作来处理愚蠢的错误而没有得到任何好处。

        您可能希望在特定情况下执行此操作,但在实践中并没有做太多事情,当有问题时您通常希望使用调试器或更完整的工具。

        【讨论】:

          【解决方案5】:

          从根本上说,在已经处理异常时抛出异常是不安全的。

          如果您的析构函数在堆栈展开时被调用,并且引发了另一个异常,那么您的线程可以在某些系统(例如 Symbian)上立即终止;您的代码计划捕获异常不会停止该终止 - 您的代码甚至不会被调用。如果您的exception escapes the destructor,您的线程肯定会被终止。

          因此,析构函数中的try {} catch(...) {} 不是可移植的,而且肯定很棘手。

          合理的建议是永远不要调用可能在cleanup代码中引发异常的代码,例如析构函数和任何可能从析构函数调用的函数,例如'cleanup()'、'@987654324 @' 或 'release_something()'。

          原帖还查询了try-catch的性能。在采用 C++ 异常的早期,异常处理代码相当昂贵。如今,您的编译器几乎可以肯定使用零成本异常,这意味着未抛出的异常不会对代码增加运行时性能损失(但当然会略微增加程序的二进制大小)。

          【讨论】:

          • “如果你的析构函数在堆栈展开时被调用,并且抛出另一个异常,那么你的线程将立即终止”。请停止说这些。这不是真的。上次你说的时候,一大群人告诉你这不是真的,并提供了参考,并指出你的参考没有说出你认为的内容。 "从析构函数中抛出" != "在析构函数中抛出并在离开析构函数之前被捕获"。
          • 抱歉,我在 Symbian 上工作了很长时间,如果您在放松时提出异常,那肯定会杀死您。
          • 嘿,我也在 Symbian 上工作过。在编写 Symbian 风格的代码时,我只是假设如果你在任何地方引发异常,Symbian 会杀了你。我不喜欢离开,但就是这样。
          • 如今,Leave 是根据适当的异常来实现的(对于零成本位,而不是 longjmp),但是正如您所说,混合自己的抛出有点令人头疼,因为只有一个 int 插槽为它保留,并且有一个清理堆栈需要担心
          【解决方案6】:

          我从不使用它,但异常安全纯粹主义者会说您的 cleanup() 函数可能会在您意想不到的情况下抛出。所以这种模式是一种额外安全的技术。

          但是,cleanup 函数不应该首先抛出。所以我会将任何异常处理放在cleanup() 中,而不是在析构函数本身中。

          【讨论】:

          • “异常安全纯粹主义者”应该知道哪些代码可以抛出异常。盲目地到处使用 try/catch 并没有什么“纯粹”
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-09-03
          • 2011-07-15
          • 1970-01-01
          • 2011-03-18
          • 1970-01-01
          • 2013-05-20
          • 2021-10-07
          相关资源
          最近更新 更多