大多数人说永远不要从析构函数抛出异常——这样做会导致未定义的行为。Stroustrup指出,“vector析构函数显式地为每个元素调用析构函数。这意味着如果元素析构函数抛出,则vector销毁失败…实际上没有很好的方法来防止析构函数抛出异常,因此标准库不保证元素析构函数是否抛出”(摘自附录E3.2)。

这篇文章似乎不是这么说的——抛出析构函数或多或少是可以的。

所以我的问题是,如果从析构函数抛出导致未定义的行为,你如何处理析构函数期间发生的错误?

如果在清理操作期间发生错误,您会忽略它吗?如果它是一个可以在堆栈中处理但不能在析构函数中处理的错误,那么从析构函数抛出异常难道没有意义吗?

显然,这种错误很少见,但也有可能发生。


当前回答

你的析构函数可能在其他析构函数链中执行。抛出未被立即调用者捕获的异常可能会使多个对象处于不一致的状态,从而导致更多的问题,而不是在清理操作中忽略错误。

其他回答

从析构函数抛出异常是危险的。 如果另一个异常已经在传播,则应用程序将终止。

#include <iostream>

class Bad
{
    public:
        // Added the noexcept(false) so the code keeps its original meaning.
        // Post C++11 destructors are by default `noexcept(true)` and
        // this will (by default) call terminate if an exception is
        // escapes the destructor.
        //
        // But this example is designed to show that terminate is called
        // if two exceptions are propagating at the same time.
        ~Bad() noexcept(false)
        {
            throw 1;
        }
};
class Bad2
{
    public:
        ~Bad2()
        {
            throw 1;
        }
};


int main(int argc, char* argv[])
{
    try
    {
        Bad   bad;
    }
    catch(...)
    {
        std::cout << "Print This\n";
    }

    try
    {
        if (argc > 3)
        {
            Bad   bad; // This destructor will throw an exception that escapes (see above)
            throw 2;   // But having two exceptions propagating at the
                       // same time causes terminate to be called.
        }
        else
        {
            Bad2  bad; // The exception in this destructor will
                       // cause terminate to be called.
        }
    }
    catch(...)
    {
        std::cout << "Never print this\n";
    }

}

这基本上可以归结为:

任何危险的事情(例如,可能抛出异常)都应该通过公共方法来完成(不一定直接)。然后,类的用户可以通过使用公共方法并捕获任何潜在的异常来潜在地处理这些情况。

析构函数将通过调用这些方法(如果用户没有显式地这样做)来结束对象,但是任何抛出的异常都会被捕获并丢弃(在尝试修复问题之后)。

所以实际上你把责任转嫁给了用户。如果用户处于纠正异常的位置,他们将手动调用适当的函数并处理任何错误。如果对象的用户不担心(因为对象将被销毁),则剩下析构函数来处理事务。

一个例子:

std:: fstream

close()方法可能会抛出异常。 如果文件已打开,析构函数将调用close(),但要确保任何异常都不会从析构函数传播出去。

因此,如果文件对象的用户想要对与关闭文件相关的问题进行特殊处理,他们将手动调用close()并处理任何异常。另一方面,如果它们不关心,那么析构函数将被留下来处理这种情况。

Scott Myers在他的书《Effective c++》中有一篇关于这个主题的优秀文章。

编辑:

显然在“更有效的c++”中也有 项目11:防止异常离开析构函数

你的析构函数可能在其他析构函数链中执行。抛出未被立即调用者捕获的异常可能会使多个对象处于不一致的状态,从而导致更多的问题,而不是在清理操作中忽略错误。

Throwing out of a destructor can result in a crash, because this destructor might be called as part of "Stack unwinding". Stack unwinding is a procedure which takes place when an exception is thrown. In this procedure, all the objects that were pushed into the stack since the "try" and until the exception was thrown, will be terminated -> their destructors will be called. And during this procedure, another exception throw is not allowed, because it's not possible to handle two exceptions at a time, thus, this will provoke a call to abort(), the program will crash and the control will return to the OS.

设置告警事件。通常,警报事件是在清理对象时通知失败的更好形式

作为对主要答案的补充,这些答案是好的,全面的和准确的,我想评论一下你引用的文章——那篇文章说“在析构函数中抛出异常并不是那么糟糕”。

本文以“抛出异常的替代方法是什么”为题,并列举了每种替代方法的一些问题。这样做后,它得出结论,因为我们找不到一个没有问题的替代方案,所以我们应该继续抛出异常。

问题在于,它列出的所有问题都没有异常行为那么糟糕,让我们记住,异常行为是“程序的未定义行为”。作者的一些反对意见包括“美学上的丑陋”和“鼓励糟糕的风格”。现在你想要哪一个?一个风格糟糕的程序,还是一个表现出未定义行为的程序?