7

我知道从析构函数中抛出通常是一个坏主意,但我想知道我是否可以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");
           }
      }
   }
};

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

4

4 回答 4

4

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

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

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

于 2013-03-21T16:33:41.897 回答
2

请注意,您的代码并没有按照您的想法执行。如果SomethingBadHappened没有适当的堆栈展开,您尝试从析构函数中抛出,但仍然std::terminate会被调用。这是 C++11 中的新行为(请参阅本文)。您将需要使用noexcept(false)规范注释您的析构函数。

假设你这样做,不清楚你所说的“安全”是什么意思。您的析构函数永远不会std::terminate直接触发。但是调用std::terminate不是 UB:它的定义非常明确且非常有用(请参阅这篇文章)。

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

此外,该设计看起来很可疑:if 语句的真正含义是“有时报告失败,有时不报告”。你对这个好吗?

另请参阅这篇文章以获得类似的讨论。

于 2014-09-20T08:59:15.613 回答
1

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

您可能想看看Herb Sutter 的uncaught_exceptions提案:

动机

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

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

...

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

A type that wants to know whether its destructor is being run to unwind this object can query uncaught_exceptions in its constructor and store the result, then query uncaught_exceptions again in its destructor; if the result is different, then this destructor is being invoked as part of stack unwinding due to a new exception that was thrown later than the object’s construction.

于 2015-09-30T09:59:59.657 回答
0

这取决于您所说的“安全”是什么意思。

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

但是,仍然存在一些问题,其中包括:

  • 如果你有一组这些,那么如果一个人抛出破坏,它们可能不会全部被破坏。
  • 一些异常安全习惯用法依赖于非抛出破坏。
  • 许多人(比如我自己)不知道如果析构函数抛出,什么会被正确销毁或不会被正确销毁的所有规则,并且不相信他们可以安全地使用你的类。
于 2013-03-21T16:35:51.170 回答