Effective C++_笔记_条款08_别让异常逃离析构函数

(整理自Effctive C++,转载请注明。整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/

C++并不禁止析构函数吐出异常,但它不鼓励你这样做。考虑如下代码:

   1: class Widget{
   2: public:
   3:     ...
   4:     ~Widget() {...}    //假设这个可能吐出一个异常
   5: };
   6:  
   7: void doSomething()
   8: {
   9:     vector<Widget> v ;  //v在这里被自动销毁
  10:     ...
  11: }

当vector v被销毁,它有责任销毁其内含的所有Widgets。销毁第一个抛出异常,销毁第二个抛出异常…,异常对C++而言太多了。其实,在两个异常同时存在的情况下,程序不是结束执行就是导致不明确的行为。

C++不喜欢析构函数抛出异常,但如果你的析构函数必须执行一个动作,而该动作可能在失败时抛出异常,该怎么办?例如你有class负责数据库连接:

   1: class DBConnection{
   2: public:
   3:     ...
   4:     static DBconnection create() ; //这个函数返回DBConnection对象
   5:  
   6:     void close () ;  //关闭联机;失败就抛出异常
   7: };

为了确保客户不忘记在DBConnection对象身上调用close(),一个合理的想法是创建一个用来管理 DBConnection资源的class,并在其析构函数中调用close:

   1: class DBConn{     //这个类用来管理DBConnection对象
   2: public:
   3:     ...
   4:     ~DBConn()     //确保数据库连接总是会被关闭
   5:     {
   6:         db.close() ;
   7:     }
   8: private:
   9:     DBConnection db ;
  10: };

这样如果用户在某一区块,实例化DBConn对象时,在该区块结束时,会自动调用该对象的析构函数,确保该资源管理对象管理的DBConnection对象close。只要调用close成功,一切美好。但如果该调用导致异常,DBConn析构函数会传播该异常。三个办法可以解决这一问题:

1 如果close抛出异常就结束程序

通常通过调用abort完成:

   1: DBConn::~DBConn()
   2: {
   3:     try{ db.close }
   4:     catch(...){
   5:         制作运转记录,记下对close的调用失败;
   6:          std::abort();
   7:     }
   8: }

如果程序遭遇一个“于析构期间发生错误”后无法继续执行,就强迫结束程序。这种方法可以阻止异常从析构函数传播出去,调用abort可以抢先制“不明确行为”于死地。

2 吞下因调用close而发生的异常

   1: DBConn::~DBConn()
   2: {
   3:     try{ db.close }
   4:     catch(...){
   5:         制作运转记录,记下对close的调用失败;

   7:     }
   8: }

一般而言,将异常吞掉是个坏主意,因为它压制了“某些动作失败”的重要信息!然而有时候吞下异常也比负担“草率结束程序”或“不明确行为带来的风险好”。

3 设计接口使用户有机会处理

一个更好的策略是重新设计DBConn接口,使其客户有机会对可能出现的问题做出反应。例如DBConn自己可以提供一个close函数,因而赋予客户一个机会得以处理“因该操作而发生的异常”。DBConn也可以追踪其所管理的对象是否已被关闭,并在答案为否的情况下有析构函数关闭。

   1: class DBConn{
   2: public:
   3:     ...
   4:     void close () // 供客户使用的新函数
   5:     {
   6:         db.close() ;
   7:        closed = true ;
   8:     }
   9:     ~DBConn()
  10:     {
  11:         if(!closed){
  12:             try{
  13:                 db.close();   //关闭连接(如果客户不那么做)
  14:             }
  15:             catch(...){
  16:                    制作运转记录,记下对close调用失败;    // 如果关闭动作失败,记录下来并结束程序或吞下异常
  17:              }
  18:         }
  19:         
  20:     }
  21: ptivate:
  22:     DBConnection db ;
  23:     bool closed ;
  24: };

把调用close的责任从DBConn析构函数转移到客户手上是给他们一个处理错误的机会,否则它们没机会响应。如果他们不认为这个机会有用,可以忽略,以来DBConn的析构函数调用close。如果真有错误发生——如果close的确抛出异常——而且DBConn析构函数结束程序或吞下该异常,客户没有立场抱怨。

请记住:

(1)析构函数绝对不要吐出异常。如果一个析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们或结束程序。

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

Effective C++_笔记_条款08_别让异常逃离析构函数,布布扣,bubuko.com

时间: 2024-10-03 18:21:07

Effective C++_笔记_条款08_别让异常逃离析构函数的相关文章

Effective C++ 条款八 别让异常逃离析构函数

class DBConn //这个class用来管理DBConnction对象 { public:   //自己设计一个新的DBConn接口 方法3 void close() { db.close(); closed = true; }     ~DBConn() //确保数据库连接总是会被关闭 { //db.close();   if (!closed) { try { db.close(); } catch() { //制作运转记录,记下对close的调用失 } } } protected:

条款8:别让异常逃离析构函数

例如: class Widget { public: ~Widget(){...} }; void doSomething() { vector<Widget>v; } 如果v中有10个Widget,销毁第一个时发生析构函数抛出异常,销毁第二个时析构函数又抛出异常,那么两个异常同时存在的情况下:程序要么结束执行要么导致不明确行为. 因此:析构函数绝对不要吐出异常.如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们或结束程序. 如果客户需要对某个操作函数运行期间抛出

Effective C++_笔记_条款12_复制对象时勿忘其每一个成分

(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 编译器会在必要时候为我们的classes创建copying函数,这些“编译器生成版”的行为:将被烤对象的所有成员变量都做一份拷贝. 如果你声明自己的copying函数,意思就是告诉编译器你并不喜欢缺省实现中的某些行为.编译器仿佛被冒犯似的,会以一种奇怪的方式回敬:当你的实现代码几乎必然出错时却不告诉你.所以自己实现copying函数时,请遵循一条规则:如果你为c

Effective C++_笔记_条款11_在operator=中处理“自我赋值”

(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为什么会出现自我赋值呢?不明显的自我赋值,是“别名”带来的结果:所谓“别名”就是“有一个以上的方法指涉对象”.一般而言如果某段代码操作pointers或references而它们被用来“指向多个相同类型的对象”,就需要考虑这些对象是否为同一个.实际上两个对象来自同一个继承体系,它们甚至不需要声明为相同类型就可能造成“别名”.因为一个base class的refe

Effective C++ 阅读笔记_条款27 尽量少做转型动作

Effective C++ 阅读笔记_条款27 尽量少做转型动作 1.转型的三种形式,可以分为两大类. (1)旧式转型(old-style casts) (1.1) (T) expresstion (1.2) T (expression) (2) 新式转型(c++-style casts) (2.1)const_cast<T> (expression) (2.2)dynamic_cast<T> (expression) (2.3)reinterpret_cast<T>

Effective C++_笔记_条款07_为多态基类声明virtual析构函数

(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 这个规则只适用于polymorphic(带多态性质的)base class身上.这种base class的设计目的是为了用来“通过base class接口处理derived class对象”.假如我们在程序中设计factory(工厂)函数,让它返回base class指针,指向新生成的derived class对象,假设base class有个non-virtu

Effective C++_笔记_条款09_绝不在构造和析构过程中调用virtual函数

(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为方便采用书上的例子,先提出问题,在说解决方案. 1 问题 1: class Transaction{ 2: public: 3: Transaction(); 4: virtual void LogTransaction() const = 0 ; 5: ... 6: }; 7:  8: Transaction::Transaction() //Base cl

Effetive C++_笔记_条款06_若不想使用编译器自动生成的函数,就该明确拒绝

(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 通常如果你不希望class支持某一特定机能,只要不声明对应函数就是了.但这个策略对copy构造函数和copy assignment操作符却不起作用,你如果不声明它们,而某些人尝试调用它,编译器会为你声明它们. 这把你逼到了一个困境.如果你不声明copy构造函数和copy assignment操作符,编译器可能为你产出一份,于是你的clas支持copying.如果

Effective C++ Item 8 别让异常逃离析构函数

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验1: 析构函数绝对不要吐出异常.如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序. 示例: class DBConnection{ public: static DBConnection create(); //返回DBConnection对象 void close(); }; class DBConn{ //这个class用来管理D