当异常被抛出时如何检测析构函数的运行?

22

在 C++ 中,如何检测析构函数是由于异常抛出导致栈被卷回而运行,而不是由于作用域正常退出触发析构函数?我想知道这一点,以便创建一个类,该类具有某些清理代码,总是在正常退出时运行,但在异常发生时跳过。


有趣的问题!我很想说这是不可能的,因为对象并不真正知道异常是否处于活动状态,它们只是在异常展开堆栈时像离开任何其他作用域一样离开作用域。如果需要的话,您可能需要一些特定于平台的“技巧”... - Kerrek SB
1
你想知道为什么在堆栈展开期间要避免清理吗? - Eric Z
@Eric Z:我曾考虑过将其用于自动stack日志记录。类似于 Log xxx("funcA - ", arg1, arg2, arg3);,但是仪器化函数很繁琐。 - Matthieu M.
3
当抛出异常时,在析构函数中不要提交待处理的数据库更新。 - WilliamKF
@WilliamKF,好的,这是一个数据库事务而不是我想象中的资源清理。谢谢你让我知道。 - Eric Z
我认为在正常退出时直接调用inst.CleanupCode()会更加简单明了,而不是去搞析构函数。我认为这是一个尝试使用RAII来解决错误处理和正常程序执行的情况,只会导致头疼的案例。 - Mark Lakata
4个回答

25

std::uncaught_exception()(定义在<exception>中)可以告诉您在析构函数中是否由于异常而调用:

class A
{
public:
    ~A()
    {
        if (std::uncaught_exception()) {
            // Called because of an exception
        } else {
            // No exception
        }
    }
};

很好,我以前不知道这个存在! :-) - Kerrek SB
2
不像你想象的那么好 - 请查看我发布的文章中的链接。 - Simon
4
在C++17中,std::uncaught_exceptions()(复数形式)表示未捕获的异常数量。 - michael_s

6

也许这篇文章能帮到您。该文章将展示std::uncaught_exception()的问题,并提供了如何在析构函数中处理异常的建议。


TL;DR:您不希望在析构函数中有不同的语义。此外,uncaught_exceptions不是稳健的。 - Mark Lakata

2

除非你有充分的理由,否则不要这样做。堆栈展开是一种语言特性,所有在try块内的自动对象都将被强制释放,以便其中的资源有机会释放。

你想在堆栈展开期间跳过dtor中的清理,这将绕过它的原始意图。而且你将冒着泄漏资源的风险。

示例

class CDBConnection
{
  public:
    CDBConnection()
    {
      m_db.open();
    }
    ~CDBConnection()
    {
      if (!std::uncaught_exception())
        m_db.close();

      // if this is called during a stack unwinding,
      // your DB connection will not be closed for sure.
      // That's a resource leakage.
    }
    //..
  private:
    DB m_db;
};

void main()
{
  //..
  try
  {
    // code that may throw
    CDBConnection db;
    //..        
  }
  catch(const CDBException& exp)
  {
    // properly handle the exception
  }
}

0

这是我能想到的一种方法,但它似乎有些笨拙:

{
  myCleanupClass unwindAction;
  try {
    // do some work which may throw exception.
  } catch (...) {
    unwindAction.disableDestructorWork();
    throw;
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接