首页 > 代码库 > [008]别让异常逃离析构函数

[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应该提供一个普通函数(而不是在析构函数中)执行该操作。

 

[008]别让异常逃离析构函数