【问题标题】:why C++11 mark destructors as nothrow, and is it possible to override it?为什么 C++11 将析构函数标记为 nothrow,是否可以覆盖它?
【发布时间】:2016-11-07 09:08:44
【问题描述】:

到目前为止,我从未收到过 C++ 编译器发出的单一警告,但现在 VS 2015 编译器似乎突然开始抱怨这个问题。

似乎 C++11 隐式地将每个析构函数标记为 nothrow https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(C4297)&rd=true

这是为什么呢?这可能是一个坏主意,但我想了解为什么?如果它本质上不是坏的,有没有办法覆盖它,使析构函数不是一个 nothrow?

附:我知道异常可能是邪恶的,但有时它们很有用,尤其是在从我的应用用户那里生成崩溃报告时。

【问题讨论】:

  • 从析构函数中抛出是一个非常糟糕的主意。
  • 想解释一下为什么?
  • 我看不到当前的答案如何真正解释为什么 noexcept 被隐式添加到析构函数中。它冒着在晦涩的情况下触发 std::terminate 的风险,与在所有情况下肯定触发 std::terminate 的风险进行交易,而且没有强制堆栈展开作为奖励......
  • 用一个间歇性的 bug 换成一个可重现的 bug 对我来说似乎很划算。

标签: c++


【解决方案1】:

在退出作用域时调用析构函数。当抛出异常时,堆栈被展开,这会导致范围退出并调用析构函数。当另一个异常正在进行时引发异常时,该进程将无条件中止并使用std::terminate。这就是为什么从析构函数中抛出是一个坏主意以及为什么析构函数被隐式标记为noexcept (==noexcept(true))*。

如果您仍然想从析构函数中抛出,您可以显式标记它noexcept(false) 以覆盖默认值。


*如果你从标记为noexcept 的函数中抛出,std::terminate 会立即被调用

【讨论】:

  • 此外,stdlibrary 的少数部分在抛出析构函数时是异常安全的......
  • 用户析构函数并不总是被隐式标记为 noexcept - 它们仅在隐式析构函数也被标记时才被标记为 noexcept - 请参阅stackoverflow.com/a/32957030/1411457
【解决方案2】:

其他答案很好地解释了为什么抛出析构函数是一个非常糟糕的主意。 Sutter 和 Alexandrescu 在 C++ 编码标准 (2005) “析构函数、释放和交换永远不会失败” 中进行了解释,这是说它们没有先决条件的另一种说法。 Scott Meyers 在 Effective C++ (3rd Ed.) (2005) “防止异常离开析构函数”Effective Modern C++ 中给出了规范建议(2014) “声明函数 noexcept 如果它们不会发出异常”

工程通常涉及权衡和妥协以及艰难的决策。有时你可能需要抛出一个析构函数。你最好有一个该死的好理由,你可能错了。在这种情况下,您可以声明析构函数noexcept(false) 以覆盖默认值。然后你会得到 C++98 的行为。如果你想在堆栈当前没有被另一个异常解除的情况下有条件地抛出,你可以使用std::uncaught_exception。处理错误时发生的处理错误是一个兔子洞,不建议你冒险下去。

如果您的意图是在检测到异常状态时捕获程序状态的快照,例如通过 Google Breakpad 打电话回家,那么异常不是一个好的解决方案。自动堆栈展开将丢弃有关程序状态的有用信息。您通常希望在崩溃报告中保留堆栈的状态,这通常意味着调用std::terminatestd::abort

【讨论】:

    【解决方案3】:

    这是为什么呢?这可能是一个坏主意,但我想了解为什么? 如果它本质上不是坏的,有没有办法覆盖它,这样 析构函数不是nothrow?

    在析构函数中抛出异常是个坏主意。原因是在异常堆栈展开期间发生,即调用堆栈上所有对象的析构函数。现在,由于堆栈展开作为异常的一部分发生,析构函数再次抛出异常。您最终可能会遇到多个异常,然后没有明确的方式来处理这些多个异常。例如。如果有两个异常并且我有一个异常而不是另一个异常的 catch 块,我的进程应该终止还是捕获并进一步处理异常?

    【讨论】:

      【解决方案4】:

      因为如果在应用程序处理之前抛出异常,当前的异常程序将终止。

      可能我会为此得到很多-1,但无论如何我都会说出来。您可以在析构函数中抛出异常。您还可以做更多大多数人会说它是“邪恶”的事情,一切都会好起来的,甚至会比没有“邪恶”部分更好。但是你需要确切地知道你在做什么。

      【讨论】:

      • 好吧...那又怎样?我对此没有问题,在我的情况下这种情况几乎是不可能的。如果我不能抛出异常并让代码在其异常状态下执行,那么无论如何它都可能出现段错误,恕我直言,这比抛出异常还要糟糕。
      • @Petr 然后是另一个原因:糟糕的程序设计。我无法想象析构函数中出现异常的原因。顺便说一句,I know that exceptions may be evil 不是。
      • @Petr 不,你不明白。有时您无法控制何时销毁对象,在这种情况下您的应用程序将失败。如果您编写简单的应用程序,您可以预测大多数情况,但对于更复杂的应用程序,这可能会让您大吃一惊。
      • @Petr it would likely segfault 如果销毁一个对象可能会导致段错误,那么 IMO 你有一个非常严重的设计问题。你说的是你不确定你是否可以摧毁这个物体。或者更糟糕的是,您最终可能会处于不一致的状态,对象部分被破坏。现在这可能对你有用,但在某些时候它可能会适得其反。而这一切都不是因为一个错误,而是你故意这样做的?
      • 顺便说一句,没有什么能阻止你在析构函数中捕获异常并做出适当的反应。但这样做只有在与第 3 方打交道时才有用。
      猜你喜欢
      • 2015-06-28
      • 1970-01-01
      • 2014-01-09
      • 2012-07-01
      • 2017-12-17
      • 2013-07-29
      • 2010-10-22
      • 1970-01-01
      • 2013-08-08
      相关资源
      最近更新 更多