(整理自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_别让异常逃离析构函数的更多相关文章

  1. EC读书笔记系列之4:条款8 别让异常逃离析构函数

    条款8 别让异常逃离析构函数 记住: ★析构函数绝对不要吐出异常.若一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序. ★若客户需对某个操作函数运行期间 ...

  2. Effective C++ -----条款08: 别让异常逃离析构函数

    析构函数绝对不要吐出异常.如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序. 如果客户需要对某个操作函数运行期间抛出的异常作出反应,那么class应 ...

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

    class DBConn //这个class用来管理DBConnction对象 { public:   //自己设计一个新的DBConn接口 方法3 void close() { db.close() ...

  4. Effective C++ 条款08:别让异常逃离析构函数

    1.别让异常逃离析构函数的原因 <Effective C++>第三版中条款08建议不要在析构函数中抛出异常,原因是C++异常机制不能同时处理两个或两个以上的异常.多个异常同时存在的情况下, ...

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

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 编译器会在必要时候为我们的classes创建copying函数, ...

  6. Effective C++_笔记_条款02_尽量以const、enum、inline替换#define

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 这个条款或许改为“宁可以编译器替换预处理器”比较好,因为或许#d ...

  7. Effective C++_笔记_条款01_视C++为一个语言联邦

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) C++的各种能力和特性使它成为一个无可匹敌的工具,但也可能引发某 ...

  8. Effective C++阅读笔记_条款2:尽量以const,enum,inline替换#define

    1.#define缺点1 #define NUM 1.2 记号NUM可能没有进入记号表,在调试或者错误信息中,无法知道1.2的含义. 改善:通过const int NUM = 1.2; 2.#dein ...

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

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为什么会出现自我赋值呢?不明显的自我赋值,是“别名”带来的结果: ...

随机推荐

  1. enum型常量

    就像结构体一样,定义一个枚举类型是不分配内存的,仅仅是定义了一个类型的名字,下面可以使用这个名字定义枚举类型的变量 枚举即将变量的值一一列举出来变量的值只限于列举出来的值得范围内 简单的应用如下 #i ...

  2. <转> 30 个有关 Python 的小技巧

    目录[+] 1.1 拆箱 1.2 拆箱变量交换 1.3 扩展拆箱(只兼容python3) 1.4 负数索引 1.5 切割列表 1.6 负数索引切割列表 1.7指定步长切割列表 1.8 负数步长切割列表 ...

  3. DW8051调试终结

    都不记得自己到底揪心了多久 —— 归根结底还是自己太菜了.终于找到了DW8051移植的bug. 这么大的一个图居然没有看到,真是气煞老夫也. 在原来移植的基础之上加两个反相器就OK 了

  4. android网络交互之DNS优化知识整理

    android网络交互之DNS优化知识整理 之前的工作中,经常会遇到DNS解析出问题导致网络交互的操作无法正常进行. UnknownHostException 在很多的移动开发过程中,与服务端的交互的 ...

  5. mojo 默认use utf8;

    my $endtime=strftime("%Y%m%d%H%M%S",localtime()); my $d=encode_utf8('验证'); if ($a3 =~/$d/) ...

  6. Sql 增加字段(有些只能在ORACLE中运行)

    增加字段 alter table table_name add column_name varchar(200) 删除字段 ALTER TABLE table_NAME DROP COLUMN col ...

  7. assert()用法

    assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:[1] #include <assert.h>void assert( ...

  8. C++算术运算符与算术表达式

    基本的算术运算符 在本章中主要介绍算术运算符与算术表达式,赋值运算符与赋值表达式,逗号运算符与逗号表达式,其他运算符将在以后各章中陆续介绍. 常见算数运算符 运算符 说明 举例 + 加法运算符,或正值 ...

  9. 对背包问题(Knapsack Problem)的算法探究

    对背包问题(Knapsack Problem)的算法探究 至繁归于至简,这次自己仍然用尽可能易理解和阅读的解决方式. 1.问题说明: 假设有一个背包的负重最多可达8公斤,而希望在背包中装入负重范围内可 ...

  10. 一天一个类,一点也不累 之 Vector

    一天一个类,一点也不累. 今天要说的是ArrayList的亲兄弟--Vector 亲兄弟?看看“族谱” Class Vector<E> java.lang.Object java.util ...