如果你不应该在析构函数中抛出异常,你如何处理其中的错误?

新手上路,请多包涵

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

这篇文章 似乎另有说法 - 抛出析构函数或多或少是可以的。

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

如果在清理操作过程中发生错误,您是否忽略它?如果它是一个可以在堆栈中处理但不能在析构函数中正确处理的错误,那么从析构函数中抛出异常是否有意义?

显然,这类错误很少见,但也有可能。

原文由 Greg Rogers 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 991
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";
    }

}

这基本上归结为:

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

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

因此,实际上您将责任转嫁给了用户。如果用户能够纠正异常,他们将手动调用适当的函数并处理任何错误。如果对象的用户不担心(因为对象将被销毁),那么析构函数将负责处理业务。

一个例子:

标准::fstream

close() 方法可能会引发异常。如果文件已打开,则析构函数调用 close(),但要确保任何异常都不会传播到析构函数之外。

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

Scott Myers 在他的“Effective C++”一书中有一篇关于这个主题的优秀文章

编辑:

显然也在“更有效的 C++”中

第 11 条:防止异常离开析构函数

原文由 Martin York 发布,翻译遵循 CC BY-SA 4.0 许可协议

从析构函数中抛出异常永远不会导致未定义的行为。

将异常抛出析构函数的问题是,成功创建的对象的析构函数在处理未捕获的异常时(在创建异常对象之后直到异常激活的处理程序完成)会被异常处理调用机制;并且,如果在处理未捕获的异常时调用的析构函数的此类附加异常中断了处理未捕获的异常,它将导致调用 std::terminate (调用 std::exception 的另一种情况是该异常不由任何处理程序处理,但这与任何其他函数一样,无论它是否是析构函数)。


如果正在处理未捕获的异常,您的代码永远不知道是否会捕获额外的异常或将归档未捕获的异常处理机制,因此永远无法确定抛出是否安全。

虽然,有可能知道正在处理未捕获的异常( https://en.cppreference.com/w/cpp/error/uncaught_exception ),因此您可以通过检查条件来过度杀伤并仅在它出现时抛出并非如此(在某些情况下它不会抛出它是安全的)。

但在实践中,这种分离为两种可能的行为是没有用的——它只是无助于你制作一个设计良好的程序。


如果您抛出析构函数而忽略未捕获的异常处理是否正在进行,为了避免可能的调用 std::terminate ,您必须保证在对象的生命周期内抛出的所有异常都可能抛出异常他们的析构函数在对象开始销毁之前被捕获。它的使用非常有限;您几乎不能使用所有可以合理地以这种方式从析构函数中抛出的类;并且仅对某些类的此类使用受限的类允许此类异常的组合也阻碍了设计良好的程序。

原文由 Arthur P. Golubev 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题