C++中禁止异常信息传递到析构函数外

2016-02-19 12:34 3 1 收藏

get新技能是需要付出行动的,即使看得再多也还是要动手试一试。今天图老师小编跟大家分享的是C++中禁止异常信息传递到析构函数外,一起来学习了解下吧!

【 tulaoshi.com - 编程语言 】


  在有两种情况下会调用析构函数。第一种是在正常情况下删除一个对象,例如对象超出了作用域或被显式地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析构函数的外面。唯一的方法是用try和catch blocks。一种很自然的做法会这样编写函数:
  
  Session::~Session()
  {
   try {
  logDestruction(this);
   }
   catch (...) {
  cerr "Unable to log destruction of Session object "
   "at address "
   this
   ".";
   }
  }
  但是这样做并不比你原来的代码安全。假如在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”这段)。

来源:https://www.tulaoshi.com/n/20160219/1601216.html

延伸阅读
从语法上看,在函数里声明参数与在catch子句中声明参数几乎没有什么差别: class Widget { ... }; //一个类,具体是什么类 // 在这里并不重要 void f1(Widget w); // 一些函数,其参数分别为 void f2(Widget& w); // Widget, Widget&,或 void f3(const Widget& w); // Widget* 类型 void f4(Widge...
当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道.这些往往会使人受尽折磨.所以如果你想深入C/C++编程,你必须静下心来,好好苦一番. 现在我们将讨论C/C++里我认为哪一本书都没有完全说清楚,也是涉及概念细节最多,语言中最难的技术之一的动态内存的传递.并且在软件开发中很多专业人员并不能写出相关的合格的代码. ...
下面是c++的源码: 代码如下: class X  { public:    int i;    int j;    ~X() {} }; void f(X x) {   X x1;   x.i = 1;   x.j = 2; } int main() {     f(X()); } 下面是main函数的汇编码: 代码如下: _main    PROC ; 15   : int ma...
简介 对于很多初学者来说,往往觉得回调函数很神秘,很想知道回调函数的工作原理。本文将要解释什么是回调函数、它们有什么好处、为什么要使用它们等等问题,在开始之前,假设你已经熟知了函数指针。 什么是回调函数? 简而言之,回调函数就是一个通过函数指针调用的函数。假如你把函数的指针(地址)作为参数传递给另一个...
函数存放在内存的代码区域内,它们同样有地址,我们如何能获得函数的地址呢? 假如我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,这一点如同数组一样,数组的名字就是数组的起始地址。 !-- frame contents -- !-- /frame contents -- 定义一个指向函数的指针用如下的形式,以上面的test()为例: ...

经验教程

413

收藏

41
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部