《Effective C++》学习笔记(条款08:别让异常逃离析构函数)

最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!

1 C++并不禁止析构函数抛出异常,但它不鼓励你这样做

 class Widget{
   public:
     ...
     ~Widget(){...}        //假设此析构函数可能会抛出异常
 };
 
 void doSomething(){
   	std::vector<Widget> v;
     ...
 }                         //在这一行调用了v的析构函数,资源被释放

vector v 被释放的时候,它容器内的 Widget 也需要释放。假设 v 内有十个 Widget ,而在析构第一个 Widget 期间,有异常抛出,其它九个 Widget 还是需要被释放的(否则会导致内存泄漏),因此需要继续释放剩下的九个 Widget 的资源,但第二个 Widget 的析构函数也抛出异常。现在有两个异常出现,但C++最多只能同时处理一个异常,因此程序这时会自动调用 std::terminate() 函数,导致我们程序的闪退或者崩溃。

不只是 vectorSTD 中的任何其它容器(如 listset)或 TR1 的任何容器或甚至 array ,也会出现相同情况。

2 如果析构函数中的代码不可避免会抛出异常呢?

如数据库连接类 DBConnection ,建立了一个连接,那就必须调用 close() 函数关掉连接,而它可能会抛出异常。为了确保 close() 被调用,可以创建一个管理类 DBConn 来管理 DBConnection ,并在其析构函数中调用 close() 来关闭数据库连接,如下述代码:

 class DBConnection{                   //数据库连接类
 public:
     ...
     static DBConnection create();     //建立一个连接
     void close();                     //关闭一个连接
 };
 
 class DBConn{                         //创建一个资源管理类来管理DBConnection对象
 public:
     ....
     ~DBConn(){ 
         db.close();//确保数据库连接总是会关闭
     }
 private:
     DBConnection db;
 };
 
 
 {                                 
   DBConn dbc(DBConnection::create()); //创建一个DBConn类的对象
   ...     //使用这个对象
 }        //对象dbc被释放资源,但它的析构函数调用了可能会抛出异常的close()方法

DBConn 的析构函数已经确保了 close() 函数被调用,但 close() 抛出异常该如何解决呢?

  • 调用 std::abort() 主动退出程序
DBConn::~DBConn(){
	try{
		db.close();
 	}catch(...){
   		std::abort();//记录访问历史,记录close()的调用失败
 	}
}
  • 消化掉抛出的异常(不推荐)
 DBConn::~DBConn(){
	try{ 
		db.close();
	}catch(...){
		//记录访问历史
	}
}

抛出的异常被 catch 块捕捉到,并在块内消化掉该异常,防止异常冲突导致程序崩溃。
但这种做法可能会给程序的稳定带来隐患,因为当某些比较严重或者不能处理的异常发生时,我们继续让程序运行,就可能导致程序的未知行为。当然如果能保证所有异常都能被正确处理,程序能继续稳定运行,就可以使用这个方法。

  • 把可能抛出异常的代码块移出析构函数(最佳策略)
  class DBConn{
  public:
      ...
      ~DBConn();
      void close();	//关闭数据库连接,需要用户自己调用
  private:
      DBConnection db; 
      bool isClose = false;	//数据库连接是否被关闭
  };
  
  void DBConn::close(){    //当需要关闭连接,手动调用此函数
    db.close();
    isClose = true;
  }
  
  DBConn::~DBcon(){
    if(!closed){//析构函数虽然还是要留有备用(双重保险),但不用每次都承担风险了
        try{
        	db.close();
        }catch(...){
        	//记录访问历史
        	//消化异常或者主动关闭
        }
    }
  }

我们可以让 DBConn 自己提供一个 close() 函数,让用户自己手动释放数据库连接,赋予了用户一个机会的处理“因该操作而发生的异常”。若用户自己手动释放了连接,则 isClose = true ;若用户 忘记释放了,则在 DBConn 的析构函数中再次释放,这就回到了"调用 std::abort() 主动退出程序"或"消化掉抛出的异常"的老路。

Note:

  • 析构函数绝对不要抛出异常。若一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后消化它们或主动结束程序
  • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么 class 应该提供一个普通函数(而非在析构函数中)执行该操作

条款09:绝不在构造函数和析构函数中调用虚函数

你可能感兴趣的:(Effective,C++,学习笔记,c++,开发语言)