1.为什么c++不喜欢析构函数抛出异常

C++并没有禁止析构函数出现异常,但是它肯定不鼓励这么做。这是有原因的,考虑下面的代码:

 class Widget {

 public:

 ...

 ~Widget() { ... } // assume this might emit an exception

 };

 void doSomething()

 {

 std::vector<Widget> v;

 ...

 } // v is automatically destroyed here

当vector V被销毁,V有责任将它包含的所有Widgets都销毁。假设v含有有10个Widgets对象,当销毁第一个Widgets对象时,抛出了一个异常。其余的9个仍然需要被释放掉(否则它们拥有的资源会被泄露),所以V应该触发其余9个对象所有的析构函数。但是假设在这9个析构函数调用过程中,第二个Widget的析构函数抛出了一个异常。现在有两个主动抛出的异常了,这对c++来说太多了。在两个异常同时出现的情况下,程序的执行要么终止要么产生未定义行为。在这个例子中,它会产生未定义行为。使用任何其他标准库容器(如list或set)或者TR1中的容器,甚至一个数组也将会产生同样的未定义行为。出现这种麻烦并不只是在容器或者数组中出现。在不使用容器或者数组的情况下,析构函数抛出的异常也可以使程序过早终止或者出现未定义行为。C++不喜欢析构函数发出异常!

2.一个例子-DB资源管理类

这很容易理解,但是析构函数需要执行的操作有可能由于异常被抛出而导致失败,这时候我们应该怎么做?举个例子,假设你在实现一个关于数据库连接的类:

 class DBConnection {

 public:

 ...

 static DBConnection create(); // function to return

 // DBConnection objects; params

 // omitted for simplicity

 void close(); // close connection; throw an

 }; // exception if closing fails

为了确保客户端不会忘记调用DBConnection对象的close函数,为DBConnestion创建一个资源管理类是一个理想的方法,close函数会在资源管理类的析构函数中被调用。这样的资源管理类将在第三章有详细的讲述,在这里,考虑这样一个类的析构函数会长成什么样子就足够了:

 class DBConn { // class to manage DBConnection

 public: // objects

 ...

 ~DBConn() // make sure database connections

 { // are always closed

 db.close();

 }

 private:

 DBConnection db;

 };

于是客户端代码可以写成这样:

 { // open a block

 DBConn dbc(DBConnection::create()); // create DBConnection object

 // and turn it over to a DBConn

 // object to manage

 ... // use the DBConnection object

 // via the DBConn interface

 } // at end of block, the DBConn

 // object is destroyed, thus

 // automatically calling close on

 // the DBConnection object

只要close函数的调用成功了这个实现就是很好的,但是如果调用产生一个异常,DBConn的析构函数会传播这个异常,也就是允许异常离开析构函数。这是一个问题,因为在析构函数中发生throw就意味这麻烦。

3.如何阻止析构函数中的异常被传播出去

有两种方法来避免这个麻烦。DBConn的析构函数可以这么做:

3.1用abort函数使程序终止

如果close函数抛出异常就将程序终止,可以调用abort函数:

 DBConn::~DBConn()

 {

 try { db.close(); }

 catch (...) {

 make log entry that the call to close failed;

 std::abort();

 }

 }

如果在执行析构函数的时候遇到一个错误程序就不能继续运行了,上面的做法会是一个合理的选择。它的优点是能够阻止异常从析构函数传播出去,传播异常会导致未定义行为。因此,对于未定义行为,调用abort能够先发制人。

3.2 将异常吞掉

将调用close时抛出的异常吞掉

 DBConn::~DBConn()

 {

 try { db.close(); }

 catch (...) {

 make log entry that the call to close failed;

 }

 }

在一般情况下,将异常吞掉是一个坏的方法,因为它会抑制重要错误信息-有一些失败的事情-的出现!但是有时候,比起程序过早终止或者未定义行为,将异常吞掉会是更好的方法。这是一个可行的选择,程序必须能够可靠的继续执行下去甚至在碰到错误出现然后将其忽略的情况。

这两种方法都不是特别吸引人。这两种的方法的问题是,程序没有办法在第一时间对导致close抛出异常的条件做出反应。

 

4.一个更好的方法-使类能够对异常做出反应

 

一个更好的方法是对DBConn的接口进行设计,于是客户端有机会对可能出现的问题做出反应。举个例子,DBConn类自己可以提供一个close函数,这就可以给客户端一个处理从close抛出异常的机会,同时也能够追踪DBConnection是否已经被关掉了,如果在close中没有被关掉就在析构函数中再次执行。这就阻止了连接无法被正确释放。如果在DBConn的析构函数中对close的调用将会失败,我们还得使用终止程序或者吞掉异常的方法:

 class DBConn {

 public:

 ...

 void close() // new function for

 { // client use

 db.close();

 closed = true;

 }

 ~DBConn()

 {

 if (!closed) {

 try { // close the connection

 db.close(); // if the client didn’t

 }

 catch (...) { // if closing fails,

 make log entry that call to close failed; // note that and

 ... // terminate or swallow

 }

 }

 }

 private:

 DBConnection db;

 bool closed;

 };

将调用close的责任从DBConn的析构函数转移到DBConn的客户端(因为DBConn的析构函数有一个“备份”调用)可能会给你肆无忌惮转移负担的印象。你可能甚至将这种做法当成Item18给出意见的反例(使接口容易被正确使用)。事实上,这两种想法都是错的。如果一个操作有可能因为抛出异常而导致失败,而我们有可能需要去处理这个异常,这个异常必须来自非析构函数才可以。因为析构函数抛出异常是很危险的,常常会导致程序过早终止或者未定义行为。在这个例子中,告诉客户端自己调用close函数并没有给它们增加负担;这反而给了它们一个处理错误的机会,否则就没有机会对错误做出反应了。如果他们发现这个机会没有什么用(可能因为他们相信没有错误会发生),他们可以忽略它,仅依靠DBConn的析构函数在调用close。如果这时出现了错误-close确实抛出了异常-他们没有资格抱怨DBConn吞掉了异常或者终止了程序。毕竟,他们原来有机会处理这个问题,但是他们没有这么做。 

5.总结

  • 析构函数不能够发出任何异常。如果在析构函数中调用某个函数可能会发生throw,析构函数应该catch所有异常然后吞掉他们或者终止程序。
  • 如果类的客户端需要对一个操作的异常throw做出反应,这个类应该提供一个普通函数来执行这个操作。

读书笔记 effective c++ Item 8 不要让异常(exceptions)离开析构函数的更多相关文章

  1. 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数

    关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...

  2. 读书笔记 effective c++ Item 7 在多态基类中将析构函数声明为虚析构函数

    1. 继承体系中关于对象释放遇到的问题描述 1.1 手动释放 关于时间记录有很多种方法,因此为不同的计时方法创建一个TimeKeeper基类和一些派生类就再合理不过了: class TimeKeepe ...

  3. 读书笔记 effective c++ Item 14 对资源管理类的拷贝行为要谨慎

    1. 自己实现一个资源管理类 Item 13中介绍了 “资源获取之时也是初始化之时(RAII)”的概念,这个概念被当作资源管理类的“脊柱“,也描述了auto_ptr和tr1::shared_ptr是如 ...

  4. 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库

    1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...

  5. 读书笔记 effective c++ Item 1 将c++视为一个语言联邦

    Item 1 将c++视为一个语言联邦 如今的c++已经是一个多重泛型变成语言.支持过程化,面向对象,函数式,泛型和元编程的组合.这种强大使得c++无可匹敌,却也带来了一些问题.所有“合适的”规则看上 ...

  6. 读书笔记 effective c++ Item 11 在operator=中处理自我赋值

    1.自我赋值是如何发生的 当一个对象委派给自己的时候,自我赋值就会发生: class Widget { ... }; Widget w; ... w = w; // assignment to sel ...

  7. 读书笔记 effective c++ Item 12 拷贝对象的所有部分

    1.默认构造函数介绍 在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:这两个函数分别叫做拷贝构造函数和拷贝赋值运算符.我们把这两个函数统一叫做拷贝函数.从Item5中,我 ...

  8. 读书笔记 effective c++ Item 13 用对象来管理资源

    1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, class Investment { ... ...

  9. 读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问

    1.为什么需要访问资源管理类中的原生资源  资源管理类是很奇妙的.它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征.在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不 ...

随机推荐

  1. 四、Hbase

    一.什么情况下使用Hbase 例子: 这里Order By无时不刻的处理,我们要看到刚才的足迹,不能使用缓存技巧. 根据时间戳来查询,显然很快,应为Hbase就是以时间戳来存的. 将最近的数据放在内存 ...

  2. axis2开发实例(二)建立独自的新工程

    第一部分 环境搭建 1.  环境搭建 (1)    下载Axis2服务包:axis2-1.6.2-bin.zip,axis2-1.6.2-war.zip,分别解压到D:\webservice_axis ...

  3. Windows Server 2012 安装sqlserver2008 小记

    1.拷贝大文件被阻止   解决方案:把大文件压缩成小文件... 据说关闭防火墙会好点,没试验过. 2.安装第一步,提示没有安装.net framework 3.5 sp1 ,使用服务器管理器,添加角色 ...

  4. ios UIKit动力 分类: ios技术 2015-07-14 12:55 196人阅读 评论(0) 收藏

    UIkit动力学是UIkit框架中模拟真实世界的一些特性. UIDynamicAnimator 主要有UIDynamicAnimator类,通过这个类中的不同行为来实现一些动态特性. 它一般有两种初始 ...

  5. 【转】iOS开发路线简述

    简单看了下楼主说的很详细,尤其是最后面那个图描述很直观,让想学习ISO开发的程序猿很清晰每个步骤学习的内容,在此收藏下. iOS系统以及iPhone的出来都要感谢乔布斯,一个完美主义者,从如此优秀的i ...

  6. Practice Round China New Grad Test 2014 报告

    今天有Google of Greater China Test for New Grads of 2014的练习赛,主要是为了过几天的校园招聘测试做练习用的,帮助熟悉平台,题目嘛,个人觉得除了A题外, ...

  7. linux设置好IP后,可以访问内网,不能访问外网

    1,设置网卡,ip vi /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 #描述网卡对应的设备别名,例如ifcfg-eth0的文件中它为et ...

  8. CodeForces 755C PolandBall and Forest (并查集)

    题意:给定每一点离他最远的点,问是这个森林里有多少棵树. 析:并查集,最后统计不同根结点的数目即可. 代码如下: #pragma comment(linker, "/STACK:102400 ...

  9. Python第三天 序列 数据类型 数值 字符串 列表 元组 字典

    Python第三天 序列  数据类型  数值  字符串  列表  元组  字典 数据类型数值字符串列表元组字典 序列序列:字符串.列表.元组序列的两个主要特点是索引操作符和切片操作符- 索引操作符让我 ...

  10. 创建TabBar

    -(void)creatTabBarView { NSArray *imgArray=@[]; NSArray *selectImage=@[]; NSArray *names=@[]; for (i ...