在 C++ 中调用可以抛出异常的函数的析构函数;

发布于 07-29 08:42 字数 210 浏览 11 评论 0原文

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

如果我的析构函数调用了一个可以抛出异常的函数,那么我在析构函数中捕获它并且不再抛出异常可以吗? 或者它是否会导致中止,并且我根本不应该从析构函数中调用此类函数?

I know that I shouldn't throw exceptions from a destructor.

If my destructor calls a function that can throw an exception, is it OK if I catch it in the destructor and don't throw it further? Or can it cause abort anyway and I shouldn't call such functions from a destructor at all?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

惟欲睡2024-08-05 08:42:42

简单的答案,永远不要允许 dtor 的例外!

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

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

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

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

*这里有一个简短的 异常保证概要(这里有一个更长的文章)

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

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

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

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

Simple answer, never allow an exception from a dtor!

The complicated answer. You only get really nailed if the exception escapes the dtor while another exception is active. The normal case for this is when you are already unwinding the stack from another exception and the object in question is destroyed. In that case if the exception escapes the dtor then std::terminate is called, note you can put in your own handler for std::terminate by calling std::set_terminate. The default implementation of std::terminate is to call abort.

To complicate things more, most functions that want to make any guarantee about their exception safety, mainly the basic guarantee or the strong guarantee, rely on the underlying types to themselves not throw in their dtor*

The real question is, what state would your program be in when this error occurs? How can you recover? Where should this recovery be handled? You need to look at your specific case and work these issues out. Sometimes it's just fine to catch the exception and ignore it. Other times you need to raise some red flags.

So the answer is: it allowed by C++ to throw an exception in a dtor, but you shouldn't ever allow it to escape.

*Here's a brief synopsis of the exception guarantees (here's a much longer article)

  1. Recap: Briefly define the Abrahams exception safety guarantees (basic,
    strong, and nothrow).

The basic guarantee is that failed
operations may alter program state,
but no leaks occur and affected
objects/modules are still destructible
and usable, in a consistent (but not
necessarily predictable) state.

The strong guarantee involves
transactional commit/rollback
semantics: failed operations guarantee
program state is unchanged with
respect to the objects operated upon.
This means no side effects that affect
the objects, including the validity or
contents of related helper objects
such as iterators pointing into
containers being manipulated.

The nothrow guarantee means that
failed operations will not happen. The
operation will not throw an exception.

演多会厌2024-08-05 08:42:42

您可以从 C++ FAQ Lite 找到此页面内容丰富。 基本的答案是“不要这样做”。 你到底打算在哪里捕获这个异常? 当您捕获该异常时,无论您打算做什么,只需使用函数调用或其他东西来完成(例如记录它或设置一个标志来警告用户或其他)。 从析构函数抛出异常存在导致整个程序终止的风险。

You may find this page from C++ FAQ Lite to be informative. The basic answer is, "don't do it." Where exactly do you plan to catch this exception? Whatever you plan on doing when you catch that exception, just do it with a function call instead or something (e.g. log it or set a flag to warn the user or whatever). Throwing exceptions from the destructor runs the risk of causing your entire program to terminate.

瞄了个咪的2024-08-05 08:42:41

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

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

Yes, that's legal. An exception must not escape from the destructor, but whatever happens inside the destructor, or in functions it calls, is up to you.

(Technically, an exception can escape from a destructor call as well. If that happens during stack unwinding because another exception was thrown, std::terminate is called. So it is well-defined by the standard, but it's a really bad idea.)

蓝海2024-08-05 08:42:41

是的。

查看标准库中的 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");
    }
}

Yes.

Look at the std::fstream class in the standard library for an example.

  • close() could potentially throw an exception.
  • The destroctor can call close() but the destructor does not throw (it will swallow any exceptions).

The concept is that if the destructor calls any methods that can throw then these methods should be public. Thus if the user of your object wants to check for exceptions they can use the public methods and handle the exception. If they do not care about the exception then just let the destructor handle the problem.

Going back to the std::fstream example.

{
    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");
    }
}
鼻尖触碰2024-08-05 08:42:41

您可以在此处找到一些示例: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() 的情况下离开析构函数。 但是,对于在堆上分配的对象,这将导致内存泄漏,因为对于从其析构函数中抛出异常的对象,不会调用“operator delete”。 令人惊讶的是,在这种情况下,基类的析构函数仍然被调用: 如果派生类析构函数抛出异常,基类析构函数会发生什么

如果异常在析构函数内部被捕获(以便异常不会离开析构函数),则不会即使另一个异常的堆栈展开正在进行中也会出现问题。 这里更深入地描述了这种情况: http: //bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MEC/MI11_FR.HTM

You can find some examples here: https://software.intel.com/sites/products/documentation/doclib/iss/2013/sa-ptr/sa-ptr_win_lin/GUID-D2983B74-74E9-4868-90E0-D65A80F8F69F.htm

If an exception leaves destructor during stack unwinding of another exception being propagated, then std::terminate() is called.

When no stack unwinding is in progress, an exception can leave destructor without std::terminate() getting called. However, for objects allocated on heap this will result in memory leak because "operator delete" will not be called for the object who throws exception out of its destructor. Surprisingly, the destructor of base class still gets called in this case: What happens to base class destructor if a derived class destructor throws an exception

If the exception is catched inside the destructor (so that the exception does not leave the destructor), then no problem even if stack unwinding of another exception is in progress. This case is described more deeply here: http://bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MEC/MI11_FR.HTM

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文