在析构函数中捕获异常

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在析构函数中捕获异常相关的知识,希望对你有一定的参考价值。

是否有可能使析构函数捕获异常然后重新抛出它们? 如果是这样,我将如何做到这一点,因为try声明没有明确的位置?

基本上,我想理想地做:

CMyObject::~CMyObject()  
{
    catch(...)    // Catch without a try.  Possible?
    {
        LogSomeInfo();
        throw;  // re-throw the same exception
    }
    // Normal Destructor operations
}

背景 我有一个庞大的,复杂的应用程序,在某处抛出未处理的异常。我没有轻松访问main或顶级消息泵或任何类似的东西,因此没有容易捕获所有未处理的异常的地方。

我认为任何未处理的异常必须通过一堆析构函数,因为堆栈是解开的。所以,我正在考虑在析构函数中散布一堆catch语句。然后,至少我会知道抛出异常时正在播放的对象。但我不知道这是否可行,或者是否可取。

答案

编辑:您可以使用std::uncaught_exception检查当前是否正在抛出异常(即,如果由于异常而正在进行堆栈展开)。无法捕获该异常或以其他方式从析构函数中访问它。因此,如果您的日志记录不需要访问异常本身,则可以使用

CMyObject::~CMyObject()  
{
  if(std::uncaught_exception()) {
    LogSomeInfo(); // No access to exception.
  }
  // Normal Destructor operations
}

请注意,这个问题是在2013年被问到的,同时std::uncaught_exceptionstd::uncaught_exceptions取代(注意最后的额外s),这将返回int。有关基本原理,请参阅http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4152.pdf,因此如果您使用的是C ++ 17,则应该更喜欢新版本。上面的论文还解释了为什么旧的std::uncaught_exception在某些情况下无法按预期工作。


另一种选择可能是std::set_terminate。如果您希望在未捕获异常并且即将终止程序时调用该方法,这将非常有用。在终止处理程序中,我通常在最终终止程序之前打印一些有关异常的信息以及它来自我的日志文件的(解码)回溯。这是编译器和系统特定的,但它是一个真正的帮助器,因为它可以节省大量时间,如果您编写服务器进程,并且通常日志文件是您从操作系统获得的。

另一答案

您可以使用std::uncaught_exception(),当且仅当正在处理异常时才返回true。它自C ++ 98以来一直可用,并被std::current_exception取代,后者返回std::exception_ptr

但是,您必须小心不要在无人看守的上下文中抛出另一个异常,否则将被捕获std::terminate。例:

X::~X() {
    if (std::uncaught_exception()) {
        try {
            LogSomeInfo();
            // and do something else...
        } catch(...) {}
    }
}
另一答案

析构函数无法捕获导致实例销毁的异常。

您只能知道在销毁期间是否存在任何“活动异常”(请参阅​​uncaught_exception)(或者,在C ++ 17中,有多少个存在uncaught_exceptions)但是异常可能确实存在之后要处理。

处理异常非常困难,比第一眼看上去更困难,原因是异常安全无法按构成进行扩展。在我看来,这意味着基本上不可能有非常琐碎的有状态子系统具有强大的异常安全性(如果异常被抛出则内部状态没有发生)。这是很久以前发现的(参见1994年Tom Cargill的“异常处理:一种虚假的安全感”)但显然仍然被大部分C ++社区所忽视。

处理我能想到的异常的唯一合理方法是让子系统具有明确定义的接口和厚厚的“墙”(内部没有副作用可能会逃脱),并且可以从头开始重新初始化为一个众所周知的状态出现问题时需要。这不是微不足道的,但可以在合理的范围内正确地完成。

在所有其他情况下,捕获异常时系统的全局状态在捕获点最多是无限期的,并且在我看来很少有用例可以在这种情况下做任何事情,除非立即尽可能大声死亡而不是在不知道发生了什么的情况下采取进一步的行动(死计划不说谎)。在我看来,即使继续调用析构函数也有些疑问。

或者你可能尝试尽可能的功能,但这也不是一个简单的路径(至少对我的大脑来说),它也远离现实(大多数计算机是具有数十亿比特可变状态的可变对象:你可以假装情况并非如此,他们反而是数学函数,没有状态,可预测的输出取决于输入...但在我看来,你只是在欺骗自己)。

以上是关于在析构函数中捕获异常的主要内容,如果未能解决你的问题,请参考以下文章

MySQLi 在析构函数中立即关闭

检测异常未被用户捕获而不重新抛出

检查指针在析构函数中不为空[重复]

在析构函数中终止当前线程

为啥我必须在析构函数中调用 MPI.Finalize() ?

即使内存不是动态分配的,在析构函数中是不是需要`delete ptr;`? [复制]