C++使用具有抛出异常析构函数的RAII技术

11

假设我有一个RAII类:

class Raii {
    Raii() {};
    ~Raii() { 
        if (<something>) throw std::exception();
    }
};

而且如果我有这个功能:

void foo() {
    Raii raii;    

    if (something) {
       throw std::exception();
    }
} 

这很糟糕,因为在清理第一个异常时我们可能会再次抛出异常,这将终止进程。

我的问题是 - 在清理可能会引发异常的代码中使用 RAII 的好模式是什么?

例如,这是好还是不好 - 为什么?

class Raii {
    Raii() {};
    ~Raii() {
        try {
           if (<something>) throw std::exception();
        }
        catch (...) {
           if (!std::uncaught_exception())
               throw;
        }
    }
};

请注意,Raii对象始终是栈分配对象-并且这不是析构函数抛出异常的一般问题。


8
这与更一般情况下的“如果我有一个析构函数可能会抛出异常,如何防止终止”并没有什么不同。除了“析构函数不应该抛出异常”之外,我认为没有更好的答案。 - sfjac
1
没有析构函数的工作,就没有RAII。我真的希望有些事情可以做。例如,基于std::uncaught_exception说些什么,你可以检测是否存在正在进行的异常。 - gsf
2个回答

10
C++几乎肯定会在C++1z(也称为C++17,如果它们按时发布的话!)中提供一个函数来获取当前异常计数:std::uncaught_exceptions(注意复数"s")。此外,默认情况下将析构函数声明为noexcept(这意味着如果您尝试通过异常退出析构函数,则会调用std::terminate)。
因此,首先将析构函数标记为抛出异常(noexcept(false))。接下来,在构造函数中跟踪活动异常的数量,并将其与析构函数中的值进行比较:如果在析构函数中有更多未捕获的异常,则知道您当前正在堆栈展开过程中,并且再次抛出将导致调用std::terminate
现在,您可以决定自己到底有多特殊以及如何处理这种情况:终止程序,还是只吞噬内部异常?
一种较差的模仿方式是,如果 uncaught_exception(单数形式)返回 true,则不抛出异常,但这会导致在由卸载触发的不同 dtor 中调用时无法处理 您的 异常。此选项在当前的 C++ 标准中可用。

@kerrek,C++17已经定案了吗? - Yakk - Adam Nevraumont
相关的论文已经被投票通过并应用(N4582),现在WD被认为是17版“功能完整”的,几乎没有被删除的可能性。 - Kerrek SB

5

ScopeGuard文章中的建议是:

在异常处理中,如果你的“撤销/恢复”操作失败,你就无能为力了。你尝试进行撤销操作,无论撤销操作成功与否,都要继续进行。

这听起来可能很疯狂,但请考虑以下情况:

  1. 我遇到内存耗尽并出现 std::bad_alloc 异常
  2. 我的清理代码记录了该错误
  3. 不幸的是,写入失败(可能是磁盘已满),并尝试抛出异常

我能撤销日志记录吗?我应该尝试吗?

当抛出异常时,你只知道程序处于无效状态。不要惊讶于一些看似不可能的事情最终变得可能。个人而言,我见过更多的情况是Alexandrescu的建议最为合理:尽力清理,但要认识到第一个异常意味着事情已经处于无效状态,因此不应对附加失败感到惊讶,尤其是由第一个问题引起的失败(“错误级联”)。试图处理它们不会有好结果。
我应该提到Cap'n Proto正是您提出的做法: 当Cap'n Proto代码可能从析构函数中抛出异常时,它首先检查std :: uncaught_exception()以确保这是安全的。 如果另一个异常已经激活,则认为新异常是主要异常的副作用,并且被静默地吞噬或在侧通道上报告。
但是,如Yakk所说,C ++ 11中的析构函数默认为nothrow(true)。 这意味着,如果您想这样做,在C ++ 11及更高版本中,您需要确保将析构函数标记为nothrow(false)。 否则,即使没有其他异常在飞行,从析构函数中抛出异常也会终止程序。请注意,“如果另一个异常已经激活,则认为新异常是主要异常的副作用,并且被静默地吞噬或在侧通道上报告”。

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