c ++ RAII析构函数异常

时间:2014-05-18 12:31:28

标签: c++ exception destructor raii

据我所知,RAII指的是在ctor中获取资源并在dtor中发布它们。

Ctor获得了一些资源并且可能会失败,从而导致异常。 Dtor释放资源也可能失败,但dtors的例外是foobar,所以也不例外。

class A {
  A() throw(Ex) { // acquire resources }
  ~A() throw() { // release resources }
}

因此,如果A类用户应该知道在A的未初始化中发生错误,我可以将未初始化外包给抛出的函数,从吞噬异常的dtor调用:

class A {
  A() throw(Ex) { // acquire resources }
  ~A() throw() { try {Release(); } catch(...) {} }

  void Release() throw(Ex) { // release resources }
}

这样,用户可以调用Exit(),如果他想要发布错误的反馈,或者只是在A超出范围时让dtor做它的工作而忽略(例如,在使用A的情况下发生其他一些异常)。 / p>

为了防止多次执行Exit()(首先明确地从用户,后来由dtor间接)我必须添加一个init-status:

class A {
  bool init;
  A() throw(Ex) { init = true; // acquire resources }
  ~A() throw() { try {Release(); } catch(...) {} }

  void Release() throw(Ex) {
    if(!init) return;
    init = false;
    // release resources
   }
}

有更好的方法可以做到这一点,还是每次资源释放失败并且我想了解它时都必须实现该模式?

2 个答案:

答案 0 :(得分:2)

释放资源不应该有任何失败的余地。例如,释放内存当然可以以不会引发异常的形式实现。 RAII旨在清理资源,而不是处理因大量清理而导致的错误。

显然,有清理行动可能会失败。例如,关闭文件可能会失败,例如,因为关闭它会刷新内部缓冲区,并且可能会失败,因为文件写入的光盘已满。如果清理操作失败,则应该有适当的释放操作,如果用户有兴趣报告清理中的错误,他们应该使用此方法:在正常路径中,将有机会处理任何错误

当释放是作为处理现有错误的一部分进行的,即抛出异常并且未达到释放操作时,析构函数需要使用任何异常。可能有一些处理方法,例如,记录抛出的异常的消息,但异常不应该逃避析构函数。

答案 1 :(得分:0)

总的来说,我认为如果您继续遵循RAII准则,那么您肯定需要在析构函数内引发异常。

例如:关闭文件,释放互斥锁,关闭套接字连接,取消映射文件映射,关闭通信端口,回滚db事务等。在RAII销毁中完成的太多工作将失败。

如果出现故障,我们该怎么办?在RAII析构函数中,我们几乎永远没有足够的信息来知道如何正确处理这些故障。我们只能选择忽略它或将其传递给更高级别。

但是如果可以安全地忽略这些错误,为什么操作系统提供的API(例如close,munmap,pthread_mutex_destroy等)会向我们返回错误代码?他们可以简单地返回void吗?

所以我们最终不得不编写这样的析构函数:

CResource::~CResource() noexcept(false)
{
    if (-1 == close(m_fd))
    {
        // ...
        if (std::uncaught_exception())
        {
            return;
        }
        throw myExp(m_fd, ...);
    }
    // ...
}

当然,除了抛出异常外,我们还可以选择自己的向上传播方法。例如,让上层组件为销毁时可能抛出的每种类型注册一个回调方法,或者维护一个全局队列来存储和传递这些异常,等等。

但是很明显,这些替代方案更加笨拙并且难以使用。这等效于自己重新实现异常机制。