C ++使用带有析构函数的RAII抛出

时间:2016-04-02 01:04:37

标签: c++ exception raii

假设我有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对象始终是堆栈分配的对象 - 这不是析构函数问题的常规抛出。

2 个答案:

答案 0 :(得分:7)

C ++几乎肯定会有一个函数来获取C ++ 1z的当前异常计数(如果他们按时发布它们也称为C ++ 17):std::uncaught_exceptions(注意复数“s”) 。此外,默认情况下,析构函数声明为noexcept(这意味着如果您尝试通过异常退出析构函数,则会调用std::terminate)。

首先,将析构函数标记为throw(noexcept(false))。接下来,跟踪ctor中活动异常的数量,将其与dtor中的值进行比较:如果dtor中有更多未捕获的异常,则表示您当前正在进行堆栈展开,并且再次抛出将导致调用到std::terminate

现在你确切地确定了你的真实性以及你希望如何处理这种情况:终止程序,或者只是吞下内部异常?

如果uncaught_exception(单数)返回true,那么模仿不好就是不抛出,但是当从展开时触发的另一个dtor调用异常时,异常不起作用,试图捕获并处理你的< / em>异常。此选项在当前的C ++标准中可用。

答案 1 :(得分:4)

the ScopeGuard article的建议是

  

在例外情况下,如果您的&#34;撤消/恢复&#34;是至关重要的。行动失败。您尝试撤消操作,无论撤消操作是否成功,您都可以继续操作。

听起来可能很疯狂,但请考虑一下:

  1. 我设法耗尽内存并获得std::bad_alloc例外
  2. 我的清理代码记录错误
  3. 不幸的是,写入失败(可能是磁盘已满),并尝试抛出异常
  4. 我可以撤消日志写吗?我应该试试吗?

    当抛出异常时,您真正知道的是程序处于无效状态。毕竟,一些不可能的事情变得可能,你不应该感到惊讶。就个人而言,我发现Alexandrescu的建议比其他情况更有意义的案例:尝试清理,但认识到第一个例外意味着事情已经处于无效状态,所以额外的失败 - - 特别是由第一个问题引起的故障(&#34;错误级联&#34;) - 不应该是一个惊喜。试图处理它们并不会很好。

    我应该提一下Cap&#39; n Proto完全符合您的建议:

      

    当Cap'n Proto代码可能从析构函数中抛出异常时,它首先检查std::uncaught_exception()以确保这是安全的。如果另一个异常已经处于活动状态,则假定新异常是主要异常的副作用,并且可以静默吞下或在旁边通道上报告。

    但是,正如Yakk所说,在C ++ 11中,析构函数默认为nothrow(true)。这意味着如果你想这样做,你需要确保在C ++ 11及更高版本中将析构函数标记为nothrow(false)。否则,即使在飞行中没有其他异常,抛出析构函数中的异常也将终止该程序。请注意,&#34;如果另一个异常已经处于活动状态,则假定新异常是主要异常的副作用,并且可以静默吞下或在旁边通道上报告。&#34;