More Effective C++ 条款11

原创 2001年10月06日 23:33:00

条款11:禁止异常信息(exceptions)传递到析构函数外<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

在有两种情况下会调用析构函数。第一种是在正常情况下删除一个对象,例如对象超出了作用域或被显式地delete。第二种是异常传递的堆栈辗转开解(stack-unwinding)过程中,由异常处理系统删除一个对象。

在上述两种情况下,调用析构函数时异常可能处于激活状态也可能没有处于激活状态。遗憾的是没有办法在析构函数内部区分出这两种情况。因此在写析构函数时你必须保守地假设有异常被激活,因为如果在一个异常被激活的同时,析构函数也抛出异常,并导致程序控制权转移到析构函数外,C++将调用terminate函数。这个函数的作用正如其名字所表示的:它终止你程序的运行,而且是立即终止,甚至连局部对象都没有被释放。

下面举一个例子,一个Session类用来跟踪在线计算机的sessions,session就是运行在从你一登录计算机开始一直到注销出系统为止的这段期间的某种东西。每个Session对象关注的是它建立与释放的日期与时间:

class Session {

public:

  Session();

  ~Session();

  ...

 

private:

  static void logCreation(Session *objAddr);

  static void logDestruction(Session *objAddr);

};

函数logCreation logDestruction被分别用于记录对象的建立与释放。我们因此可以这样编写Session的析构函数:

Session::~Session()

{

  logDestruction(this);

}

一切看上去很好,但是如果logDestruction抛出一个异常,会发生什么事呢?异常没有被Session的析构函数捕获住,所以它被传递到析构函数的调用者那里。但是如果析构函数本身的调用就是源自于某些其它异常的抛出,那么terminate函数将被自动调用,彻底终止你的程序。这不是你所希望发生的事情。程序没有记录下释放对象的信息,这是不幸的,甚至是一个大麻烦。那么事态果真严重到了必须终止程序运行的地步了么?如果没有,你必须防止在logDestruction内抛出的异常传递到Session析构函数的外面。唯一的方法是用trycatch blocks。一种很自然的做法会这样编写函数:

Session::~Session()

{

  try {

    logDestruction(this);

  }

  catch (...) {

    cerr  << "Unable to log destruction of Session object "

          << "at address "

          << this

          << "./n";

  }

}

但是这样做并不比你原来的代码安全。如果在catch中调用operator<<时导致一个异常被抛出,我们就又遇到了老问题,一个异常被转递到Session析构函数的外面。

我们可以在catch中放入try,但是这总得有一个限度,否则会陷入循环。因此我们在释放Session时必须忽略掉所有它抛出的异常:

Session::~Session()

{

  try {

    logDestruction(this);

  }

  catch (...) {  }

}

catch表面上好像没有做任何事情,这是一个假象,实际上它阻止了任何从logDestruction抛出的异常被传递到session析构函数的外面。我们现在能高枕无忧了,无论session对象是不是在堆栈辗转开解(stack unwinding)中被释放,terminate函数都不会被调用。

不允许异常传递到析构函数外面还有第二个原因。如果一个异常被析构函数抛出而没有在函数内部捕获住,那么析构函数就不会完全运行(它会停在抛出异常的那个地方上)。如果析构函数不完全运行,它就无法完成希望它做的所有事情。例如,我们对session类做一个修改,在建立session时启动一个数据库事务(database transaction,终止session时结束这个事务:

Session::Session()          // 为了简单起见,,

{                           // 这个构造函数没有

                            // 处理异常

  logCreation(this);

  startTransaction();       // 启动 database transaction

}

 

Session::~Session()

{

  logDestruction(this);

  endTransaction();         // 结束database transaction

}

如果在这里logDestruction抛出一个异常,在session构造函数内启动的transaction就没有被终止。我们也许能够通过重新调整session析构函数内的函数调用顺序来消除问题,但是如果endTransaction也抛出一个异常,我们除了回到使用trycatch外,别无选择。

综上所述,我们知道禁止异常传递到析构函数外有两个原因,第一能够在异常转递的堆栈辗转开解(stack-unwinding)的过程中,防止terminate被调用。第二它能帮助确保析构函数总能完成我们希望它做的所有事情。(如果你仍旧不很信服我所说的理由,可以去看Herb Sutter的文章Exception-Safe Generic Containers ,特别是“Destructors That Throw and Why They’re Evil”这段)。

《Effective C++》学习笔记——条款31

《Effective C++》学习笔记——条款31:将文件间的编译依存关系降至最低
  • lx417147512
  • lx417147512
  • 2015年06月15日 13:51
  • 1367

《More Effective C++》条款27:如何让类对象只在栈(堆)上分配空间?

昨天一个同学去网易面试C++研发,问到了这么一个问题:如何限制一个类对象只在栈(堆)上分配空间? 一般情况下,编写一个类,是可以在栈或者堆分配空间。但有些时候,你想编写一个只能在栈或者只能在堆上面分...
  • hxz_qlh
  • hxz_qlh
  • 2013年10月26日 21:27
  • 6048

《Effective C++》学习笔记——条款25

《Effective C++》学习笔记——条款25:考虑写出一个不抛异常的 swap 函数
  • lx417147512
  • lx417147512
  • 2014年12月30日 22:32
  • 837

《Effective C++》:条款41-条款42

条款41了解隐式接口和编译期多态 条款42了解typename的双重意义条款
  • KangRoger
  • KangRoger
  • 2015年03月10日 22:13
  • 1204

《Effective C++》:条款28-条款29

条款28避免返回handles指向对象内部成分:指的是不能返回对象内部数据/函数的引用、指针等。 条款29为异常安全而努力是值得的:指的是要有异常处理机制,避免发生异常时造成资源泄露等问题。...
  • KangRoger
  • KangRoger
  • 2015年02月19日 19:47
  • 1372

《Effective C++》让自己习惯C++:条款1-条款4

《Effective C++》条款1到条款4。基本是总结C++的一些特点,尤其是不同于C语言的特点。...
  • KangRoger
  • KangRoger
  • 2014年12月13日 19:26
  • 2350

《Effective C++》资源管理:条款13-条款15

在系统中,资源是有限的,一旦用完必须归还给系统,否则可能会造成资源耗尽或其他问题。例如,动态分配的内存如果用完不释放会造成内存泄漏。 这里说的资源不仅仅是指内存,还包括其他,例如文件描述符、网络连接、...
  • KangRoger
  • KangRoger
  • 2015年01月14日 21:46
  • 1285

《Effective C++》:条款44-条款45

条款44将与参数无关的代码抽离templates 条款45运用成员函数模板接受所有兼容类型...
  • KangRoger
  • KangRoger
  • 2015年03月12日 22:01
  • 1483

Effective Modern C++ 条款28 理解引用折叠

Effective Modern C++ 条款28
  • big_yellow_duck
  • big_yellow_duck
  • 2016年09月04日 19:34
  • 1991

《Effective C++》设计与声明:条款18-条款19

这两个条款讲的是:接口的设计和类的设计。其中接口的设计原则是让接口容易被正确使用,不容易被误用;后面有一系列的做法。类的设计,讲的是类设计犹如新类型type的设计。在设计类时要考虑的一系列问题。...
  • KangRoger
  • KangRoger
  • 2015年01月21日 21:43
  • 1323
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:More Effective C++ 条款11
举报原因:
原因补充:

(最多只允许输入30个字)