【问题标题】:Catching exceptions in destructors在析构函数中捕获异常
【发布时间】:2013-09-28 14:31:06
【问题描述】:

是否可以让析构函数捕获异常然后重新抛出它们?
如果是这样,我该怎么做,因为try 声明没有明确的位置?

基本上,我想理想地做:

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

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

我认为任何未处理的异常都必须在堆栈展开时通过一堆析构函数。所以,我正在考虑在析构函数中分散一堆catch 语句。然后至少我会知道抛出异常时有哪些对象在起作用。但我不知道这是否可行或可取。

【问题讨论】:

  • 您也许可以使用function try block,但说真的,不要这样做。你永远不应该编写一个抛出的析构函数。
  • 哦,您想首先捕获导致堆栈展开的异常吗?
  • 投票关闭 Dup 的人显然没有阅读问题:我希望在析构函数中 CATCH,而不是 throw。
  • @abelenky:实际上,您并不是要捕捉,而是要在异常展开期间执行一些额外的处理。
  • @abelenky 我编辑了我的答案并进行了一些重要的改进。

标签: c++ exception destructor


【解决方案1】:

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

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

请注意,这个问题是在 2013 年提出的,同时 std::uncaught_exception 被替换为 std::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。如果您希望在未捕获异常并即将终止程序时调用方法,这将很有用。在终止处理程序中,我通常会在最终终止程序之前打印一些关于异常的信息和它来自哪里的(解构的)回溯到我的日志文件。这是特定于编译器和系统的,但它是一个真正的帮手,因为如果您编写服务器进程并且通常日志文件就是您从操作中获得的全部内容,它可以节省大量时间。

【讨论】:

  • 但是删除中间的6行。您有一个未捕获异常,因此抛出将调用std::terminate。另外,throw; 在 catch 块之外无效。
  • uncaught_exception 看起来很有希望。我正在调查,谢谢。
  • @BenVoigt 好的,我改进了答案。
  • 是否有必要同时检查uncaught_exception 转换为eptrboolcurrent_exception 返回“如果没有处理异常,则返回 null exception_ptr 对象”[传播]/8?
  • @DanielFrey uncaught_exceptioncurrent_exception从不同时为真。 uncaught_exception 在 catch 子句开头切换为 false,而 current_exception 在此之前为 false。看this answer我的问题。
【解决方案2】:

您可以使用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(...) {}
    }
}

【讨论】:

  • try..catch 在这里并不能真正帮助您,是吗?您已经有一个异常悬而未决,因此LogSomeInfo 的任何抛出都将是双重错误并立即触发terminate
  • @ComicSansMS:我可以向你保证that it works。如果您允许异常逃脱 try/catch(例如通过从 catch 重新抛出),那么它将终止。在我看来,try 打开了一个新的异常上下文。
  • 你是对的,只有当异常通过异常处理机制直接调用的函数(即析构函数本身)时,你才会遇到麻烦,但是可以抛出异常并且低于此。抱歉,造成混乱。
  • @ComicSansMS:别担心 :)
【解决方案3】:

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

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

处理异常非常困难,比人们乍一看可能想的要困难得多,原因是异常安全不会因组成而扩展。在我看来,这意味着基本上不可能拥有具有强大异常安全性的非平凡的有状态子系统(如果抛出异常,内部状态不会发生任何事情)。这是很久以前发现的(参见 1994 年 Tom Cargill 的“异常处理:一种虚假的安全感”),但显然仍然被 C++ 社区的大部分人所忽视。

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

在所有其他情况下,捕获异常时系统的全局状态充其量在捕获点是不确定的,在我看来,在这种情况下您可以做任何事情的用例很少,除了立即死亡尽可能大声而不是在不知道发生了什么的情况下采取进一步的行动(死程序不会说谎)。在我看来,即使继续调用析构函数也有些问题。

或者您可能会尝试尽可能地发挥功能,但这也不是一条容易的道路(至少对我的大脑而言)而且它也远离现实(大多数计算机是具有数十亿位可变状态的可变对象:您可以假装不是这种情况,而是它们是没有状态且具有依赖于输入的可预测输出的数学函数……但在我看来,您只是在自欺欺人)。

【讨论】:

  • 查找销毁时是否有uncaught_exception 足以满足我的调试目的。如果存在 uncaught_exception,则很有可能它至少与对象的破坏相关。
  • @abelenky:实际上我对uncaught_exception() 的工作方式有误,因此您实际上可以检测到是否由于堆栈展开而发生破坏。
  • 很高兴知道我并不孤单地怀疑代码异常的价值。只是我的解决方案更激进:我只是禁用它们(这让我回到了所有程序流程都可以明确看到和推理的世界......)。
猜你喜欢
  • 1970-01-01
  • 2016-07-09
  • 2013-09-07
  • 1970-01-01
  • 1970-01-01
  • 2023-03-17
  • 2014-02-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多