【问题标题】:Destructor that calls a function that can throw exception in C++调用可以在 C++ 中引发异常的函数的析构函数
【发布时间】:2010-11-14 08:47:27
【问题描述】:

我知道我 shouldn't 从析构函数中抛出异常。

如果我的析构函数调用了一个可以抛出异常的函数,如果我在析构函数中捕获它并且不进一步抛出它可以吗?还是它会导致中止,我根本不应该从析构函数中调用这些函数?

【问题讨论】:

  • 只是为了澄清,您是在问析构函数是否可以捕获异常,因此它永远不会离开析构函数,或者只要它被捕获就可以让它离开析构函数?
  • 我在问如果异常留在 D'tor 中是否可以。

标签: c++ exception destructor throw try-catch


【解决方案1】:

是的,这是合法的。异常不能从析构函数中转义,但是析构函数内部或它调用的函数中发生的任何事情都取决于您。

(从技术上讲,异常也可以从析构函数调用中逃脱。如果在堆栈展开期间由于抛出另一个异常而发生这种情况,则会调用std::terminate。因此它在标准中得到了很好的定义,但它是一个 真的坏主意。)

【讨论】:

  • 抱歉吹毛求疵,但我会使用“法律”以外的术语。在析构函数中抛出异常也是“合法的”,即。 e.它将编译并运行。但这是一种不好的做法,会导致不愉快的影响。
  • 是和不是。没错,从析构函数中抛出异常在技术上是合法的(然后调用std::terminate)。但是您对“合法”的定义是错误的。仅仅因为某些东西可以编译和运行,它就不会使它成为合法的 C++。
  • 我不确定你想要的法律是什么意思,但我和 Dima 一起讨论这个问题。尽管 C++ 不使用合法的工作,但它确实定义了一个格式良好的程序(可能最接近我认为的“合法”)、未指定的行为未定义的行为。允许异常从析构函数中传播出来可能发生在格式良好的程序中,并且行为既不是未定义的,也不是未指定的。这并不能阻止它成为一种几乎普遍不受欢迎的行为。
  • 在正常情况下,异常转义析构函数绝对没有问题(不调用std::terminate())。如果另一个异常已经在传播(std::terminate() 被调用),这只是一个问题,这是因为运行时无法处理同时传播的两个并行异常的概念(或者 C++ 设计者想不出合乎逻辑的方式处理这种情况)。
  • 这个原则也被称为析构函数中发生的事情留在析构函数中。
【解决方案2】:

是的。

以标准库中的 std::fstream 类为例。

  • close() 可能会引发异常。
  • 析构函数可以调用 close() 但析构函数不会抛出(它会吞下任何异常)。

这个概念是,如果析构函数调用任何可以抛出的方法,那么这些方法应该是公共的。因此,如果您的对象的用户想要检查异常,他们可以使用公共方法并处理异常。如果他们不关心异常,那么就让析构函数处理问题。

回到 std::fstream 示例。

{
    std::fstream   text("Plop");
    // Load Text.

    // I don't care if the close fails.
    // So let the destructor handle it and discard exceptions
}



{
    // If this fails to write I should at least warn the user.
    // So in this case I will explicitly try and close it.
    try
    {
        std::ofstram    password("/etc/password");
        // Update the password file.

        password.close();
    }
    catch(...)
    {
          Message.ShowDialog("You failed to update the Password File");
    }
}

【讨论】:

    【解决方案3】:

    您可以在此处找到一些示例:https://software.intel.com/sites/products/documentation/doclib/iss/2013/sa-ptr/sa-ptr_win_lin/GUID-D2983B74-74E9-4868-90E0-D65A80F8F69F.htm

    如果一个异常在另一个正在传播的异常的堆栈展开期间离开析构函数,则调用 std::terminate()。

    当没有堆栈展开正在进行时,异常可能会在没有调用 std::terminate() 的情况下离开析构函数。但是,对于在堆上分配的对象,这将导致内存泄漏,因为不会为从其析构函数中抛出异常的对象调用“操作员删除”。令人惊讶的是,在这种情况下仍会调用基类的析构函数:What happens to base class destructor if a derived class destructor throws an exception

    如果异常在析构函数中被捕获(这样异常不会离开析构函数),那么即使另一个异常的堆栈展开正在进行中也没有问题。这个案例在这里有更深入的描述:http://bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MEC/MI11_FR.HTM

    【讨论】:

      【解决方案4】:

      简单的答案,绝不允许来自 dtor 的异常!

      复杂的答案。只有当另一个异常处于活动状态时异常从 dtor 中逃脱,你才会真正被钉牢。正常情况是当您已经从另一个异常中展开堆栈并且相关对象被销毁时。在这种情况下,如果异常从 dtor 中逃脱,则调用 std::terminate,请注意,您可以通过调用 std::set_terminatestd::terminate 放入自己的处理程序。 std::terminate 的默认实现是调用 abort。

      更复杂的是,大多数想要对其异常安全性做出任何保证的函数,主要是基本保证或强保证,都依赖于底层类型本身而不是抛出它们的 dtor*

      真正的问题是,当这个错误发生时你的程序会处于什么状态?你怎么能康复?这个恢复应该在哪里处理?您需要查看您的具体案例并解决这些问题。有时捕获异常并忽略它就可以了。其他时候你需要提出一些危险信号。

      所以答案是:C++ 允许它在 dtor 中抛出异常,但你不应该让它逃脱。

      *这里是一个简短的synopsis 异常保证(这里是一个更长的article

      1. 回顾:简要定义 Abrahams 异常安全保证(基本、 强,不扔)。

      基本保证是失败 操作可能会改变程序状态, 但没有发生泄漏和影响 对象/模块仍然是可破坏的 并且可用,一致(但不是 必然是可预测的)状态。

      强有力的保证涉及 事务提交/回滚 语义:失败的操作保证 程序状态不变 相对于操作的对象。 这意味着没有影响 对象,包括有效性或 相关帮助对象的内容 例如指向的迭代器 容器被操纵。

      nothrow 保证意味着 不会发生失败的操作。这 操作不会抛出异常。

      【讨论】:

      • 据我理解的问题,他根本没有询问离开 dtor 的异常,而只是询问 dtor 是否可以调用抛出异常的函数,只要 dtor 捕获异常,因此它不会传播
      【解决方案5】:

      您可能会从 C++ FAQ Lite 中找到 this page 以提供信息。基本答案是“不要这样做”。您计划在哪里捕获此异常?无论您在捕获该异常时打算做什么,只需使用函数调用或其他方式(例如,记录它或设置一个标志以警告用户或其他方式)即可。从析构函数中抛出异常可能会导致整个程序终止。

      【讨论】:

      • 这与问题有什么关系?如果他调用一个可以抛出 std::bad_alloc 的函数,还有什么替代方法?写一个“包装器”函数?这不是解决方案,只是将异常隐藏在另一层中。
      猜你喜欢
      • 2017-12-24
      • 2013-10-30
      • 2017-08-16
      • 2012-04-11
      • 2013-11-07
      • 1970-01-01
      • 2016-10-02
      • 2014-07-06
      • 1970-01-01
      相关资源
      最近更新 更多