More Effective C++----(11)禁止异常信息传递到析构函数外
2016-05-05 22:01
543 查看
Item M11:禁止异常信息(exceptions)传递到析构函数外
转载请注明出处</article/7845890.html>在有两种情况下会调用析构函数。第一种是在正常情况下删除一个对象,例如对象超出了作用域或被显式地delete。第二种是异常传递的堆栈辗转开解(stack-unwinding)过程中,由异常处理系统删除一个对象。
在上述两种情况下,调用析构函数时异常可能处于激活状态也可能没有处于激活状态。遗憾的是没有办法在析构函数内部区分出这两种情况。因此在写析构函数时你必须保守地假设有异常被激活。因为如果在一个异常被激活的同时,析构函数也抛出异常,并导致程序控制权转移到析构函数外,C++将调用terminate函数。这个函数的作用正如其名字所表示的:它终止你程序的运行,而且是立即终止,甚至连局部对象都没有被释放。(这就是为什么析构函数默认为noexcept的形式的原因)
下面举一个例子,一个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析构函数的外面。唯一的方法是用try和catch
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也抛出一个异常,我们除了回到使用try和catch外,别无选择。
综上所述,我们知道禁止异常传递到析构函数外有两个原因,第一能够在异常转递的堆栈辗转开解(stack-unwinding)的过程中,防止terminate被调用。第二它能帮助确保析构函数总能完成我们希望它做的所有事情。(如果你仍旧不很信服我所说的理由,可以去看Herb
Sutter的文章Exception-Safe Generic Containers ,特别是“Destructors That Throw and Why They’re Evil”这段)。
总结:也就是说析构函数在调用其他函数释放资源时,必须确保其他的函数不要抛出异常,原因有以上两点,常用的方法就是用try和catch捕获"其他的函数"所抛出的所有异常。
相关文章推荐
- c 语言 dll编译与使用
- C++ Circle
- c++ 内存分区
- C语言 A律压缩
- boost静态链接库和c++/clr不兼容问题:未能加载文件或程序集,不是有效的Win32应用程序。
- More Effective C++----(10)在构造函数中防止资源泄漏
- 【C++】VS2010生成的程序在安装了vc2010运行库的机器上提示“丢失MSVCR100D.dll”
- C++数组的二分法查找
- C语言中的移位操作
- leetcode 13 Roman to Integer C++
- 【C++】VS2010将写好的程序打包成安装文件发布
- 《高质量C++/C编程指南》代码规范笔记
- PAT (Advanced Level) Practise 1102 Invert a Binary Tree PAT 1102 坑点
- leetcode 92 Reverse Linked List II C++
- C语言内存分配方式
- More Effective C++----异常 & (9)使用析构函数防止资源泄漏
- C++中引用类型
- [Assignment] C++1
- 【C/C++】类中的static成员、static const成员和const成员
- [C/C++] main函数执行前后还会发生什么