这是从析构函数中抛出异常的安全方法吗?

时间:2013-03-21 16:05:11

标签: c++ exception c++11

我知道从析构函数中抛出通常是一个坏主意,但我想知道我是否可以使用std::uncaught_exception()安全地从析构函数中抛出。

考虑以下RAII类型:

struct RAIIType {
   ...

   ~RAIIType() {
      //do stuff..
      if (SomethingBadHappened()) {
           //Assume that if an exception is already active, we don't really need to detect this error
           if (!std::uncaught_exception()) {
               throw std::runtime_error("Data corrupted");
           }
      }
   }
};

这个UB在c ++ 11中吗?这是一个糟糕的设计吗?

4 个答案:

答案 0 :(得分:4)

你有if,你是否考虑过“其他”情况?它可以抛出异常或......做什么?另一个分支可以有两件事。

  • 什么都没有(如果错误发生时什么都不需要,为什么要抛出异常?)
  • 它“处理”异常(如果可以“处理”,为什么抛出异常?)

既然我们已经确定没有目的来有条件地抛出异常,那么问题的其余部分就没有实际意义了。但是这里有一个小问题:从来没有过去的例外。如果对象抛出异常,则调用代码通常以某种方式检查该对象以“处理”异常。如果该对象不再存在,则通常无法“处理”异常,这意味着不应抛出异常。要么被忽略,要么程序生成转储文件并中止。因此,抛弃析构函数中的异常无论如何都是毫无意义的,因为捕获它是毫无意义的。考虑到这一点,类假设析构函数不会抛出,并且如果析构函数抛出,几乎每个类都会泄漏资源。所以从来没有从破坏者那里得到例外。

答案 1 :(得分:2)

请注意,您的代码并不像您认为的那样。在SomethingBadHappened并且没有堆栈展开的情况下,您尝试从析构函数中抛出,但仍然会调用std::terminate。这是C ++ 11中的新行为(请参阅this article)。您需要使用noexcept(false)规范注释析构函数。

假设你这样做,并不清楚你的意思是什么?#34;安全"。析构函数永远不会直接触发std::terminate。但是调用std::terminate不是UB:它定义得非常有用(见this article)。

当然,您不能将您的班级RAIIType放入STL容器中。 C ++标准显式调用UB(当析构函数抛出STL容器时)。

此外,设计看起来很可疑:if语句实际上意味着"有时报告失败,有时不报告"。你还好吗?

有关类似的讨论,另请参阅this post

答案 2 :(得分:1)

  

我知道从析构函数中抛出通常是一个坏主意,但我想知道我是否可以使用std::uncaught_exception()安全地从析构函数中抛出。

您可能希望查看Herb Sutter的uncaught_exceptions提案:

  

<强>动机

     众所周知,

std::uncaught_exception在许多情况下“几乎有用”,例如在实现Alexandrescu风格的ScopeGuard时。 [1]   特别是,当在析构函数中调用时,C ++程序员经常期望的和基本上正确的是:“如果在堆栈展开期间调用此析构函数,则uncaught_exception返回true。”

     

然而,正如至少自1998年以来在Guru of the Week #47中所记录的那样,它意味着从析构函数中传递调用的代码本身可以在堆栈展开期间调用,但无法正确检测它本身是否实际被调用作为展开的一部分。一旦你解除任何异常,对于uncaught_exception,一切看起来就像展开,即使有超过   一个活跃的例外。

     

...

     

本文提出了一个新的函数int std::uncaught_exceptions(),它返回当前活动的异常数,意味着抛出或重新抛出但尚未处理。

     

想要知道它的析构函数是否正在运行以展开此对象的类型可以在其构造函数中查询uncaught_exceptions并存储结果,然后在其析构函数中再次查询uncaught_exceptions;如果结果不同,那么这个析构函数将作为堆栈展开的一部分被调用   由于一个新的异常比对象的构造晚了。

答案 3 :(得分:0)

这取决于你的意思&#34;安全&#34;。

这将防止从析构函数中抛出一个问题 - 如果在处理另一个异常时堆栈展开期间发生错误,程序将不会被终止。

然而,仍有一些问题,其中包括:

  • 如果你有一个这样的阵列,那么如果一个人遭到破坏,它们可能不会全部被摧毁。
  • 一些例外安全习语依赖于非投掷破坏。
  • 很多人(比如我自己)都不知道所有规则,如果一个析构函数抛出,将会被正确销毁,或者不会确信他们可以使用你的课程安全。