[Effective C++ --008]别让异常逃离析构函数

这章非常容易理解:因为C++并不禁止析构函数吐出异常,只是不鼓励这样做而已。

一、原因

假设我们有10个装着鸡蛋的容器,而且现在我们还想着把它在析构函数打烂。

class Egg {
public :
     ...
     ~Egg() {
          // 这里可能出错,导致蛋打不烂
     }
};

void foo() {
      vector<Egg> v   // 假设v中间有10个Egg 
      ....
}                     // v在这里被自动销毁

 如果我们在销毁10个鸡蛋的过程中,在析构第一个鸡蛋的时候,有个异常被抛出,按照销毁机制,后续的9个鸡蛋还是需要被销毁的(否则鸡蛋保存的任何资源都会发生泄漏)。

但是如果后面的鸡蛋仍然抛出异常,在两个异常同时存在的情况下,C++程序会结束执行或者出现不明确的行为。

就算是使用STL的其他容器,还是会发生同样的问题。

为什么呢?因为C++不鼓励析构函数吐出异常。

二、详解

为了方便上面的原因理解,我们可以来尝试一下的例子:

class DB {
public :
     ....
     static DB create() ;//函数返回DB对象
     void close();        //关闭数据库的联机,失败则抛出异常    
}

 

 

如果为了方便其他人员使用DB类,防止在调用DB的时候忘记关闭连接,那么我们可以贴心一下:

class DBC {
public :
     ....
     ~DBC() {       // 确保每次调析构的时候都会关闭连接         
       db.close();    
    }
private:
     DB db;
}

 

其他人直接使用DBC的类就好了,但是如果这样写,就会出现章一种的问题了,如果析构中抛出了异常怎么办?

我们可以用两种方法来解决:

方法1:

DBC::~DBC() {
      try {(db.close();) }  //检查异常 
      catch (...) {
            std::abort();      //如果catch到了异常,那么直接强迫结束程序
      }
}

 

方法2:

DBC::~DBC() {
      try {(db.close();) }  //检查异常 
      catch (...) {
            ... //如果catch到了异常,记录对close调用失败
      }
}

 

上面两种方法似乎都会异常进行了"提示",但是都无法针对“导致异常”的情况作出处理。

因此,我们可以考虑重新设计DBC类:

class DBC {
public :
     ....
     void close() {
          db.close();
          closed = true;
     }
     ~DBC() {       // 确保每次调析构的时候都会关闭连接         
          if (!closed) {
               try {db.close();}
               catch(...) {
                    ...// 记录异常
               }
          }
    }
private:
     DB db;
     bool closed;
}

 

 

■总结:

1.析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该要能捕捉任何异常,然后“吞下异常”或者终止程序。

2.如果需要对某个操作函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数(而不是在析构函数中)执行该操作。

 

posted @ 2014-11-11 18:09  依然冷月  阅读(307)  评论(0编辑  收藏  举报