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

 

更多相关文章
  • 条款8 别让异常逃离析构函数 记住: ★析构函数绝对不要吐出异常.若一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序. ★若客户需对某个操作函数运行期间抛出的异常做出反应,那么class应提供一个普通函数(而非在析构函数)执行该操作. -- 问题背景: ...
  • 1.为何析构函数不应该抛出异常?    有两种情况:    1).假设析构函数中有众多语句,而第一条语句抛出异常(或者其他语句),那么抛出异常以后的语句就得不到执行.而通常我们在析构函数中写的是清理资源(或回收资源)的代码,那么部分资源就不会被回收,会造成内存泄漏或程序提前结束(abort的作用). ...
  • 章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(2)-读书笔记 <Effective C++ ...
  • Effective C++ 條款8 別讓異常逃離析構函數
    1. 当异常发生时,如果异常发生在一个try块内部,程序就会跳出该try块,并逐层寻找匹配
  • 1. 让自己习惯C++(Accustoming yourself to C++) 条款01: 视C++ 为一个语言联邦(View C++ as a federation of languages) 条款02: 尽量以const,enum,inline替换#define(Prefer consts,e
  • 


    		    《Effective C++》 筆記:Tips05Tips08
    Tips05:了解C++悄悄编写并调用哪些函数 编译器自动生成的函数包括 1. 构造函数(default) 2. 拷贝构造函数(copy) 3. 析构函数 4. 拷贝复制运算符(copy assignment) 如果一个class的成员变量含有reference(引用)类型,那么必须自己定义copy ...
  • 这是前段时间看的书,整理到这里吧,以后查看也方便. 这些条款需要反复查看. 条款01:视C++为一个语言联邦 条款02:尽量用const.enum.inline替换#define 条款03:尽可能的使用const 条
  • 条款05:了解c++默默编写并调用了哪些函数 编译器可以暗自为 class 创建default构造函数,copy构造函数,copy assignment操作和析构函数所有这些函数都是 public 并且 inline编译器自动生成的析构函数是non-virtual 的编写一个空类 class Wid
一周排行