C与C++中的异常处理13

原创 2002年03月01日 08:51:00

1.     异常安全<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

    接下来两次,我将讨论“异常安全”,C++标准中使用了(在auto_ptr中)却没有定义的术语。在C++范围内,不同的作者使用这个术语却表达不同的含义。在我的专题中,我从两个方面来定义“异常安全”:

l         如果一个实体捕获或抛出一个异常,但仍然维持它公开保证的语义,它就是“接口安全”的。依赖于它保证的力度,实体可能不允许将任何异常漏给其用户。

l         如果异常没有导致资源泄漏或产生未定义的行为,实体就是“行为安全”的。“行为安全”一般是强迫的。幸运的是,如果做到了“行为安全”,通常也间接提供了“接口安全”。

    异常安全有点象const:好的设计必须在一开始就考虑它,它不能够事后补救。但是我们开始使用异常还没有多少年,所以还没有“异常安全问题集”这样的东西来指导我们。实际上,我期望大家通过一条艰辛的道路来掌握异常安全:通过经历异常故障在编码时绕过它们;或关闭异常特性,认为它们“太难”被正确掌握。

    我不想撒谎:分析设计上的异常安全性太难了。但是,艰辛的工作也有丰厚的回报。不过,这个主题太难了,想面面俱到的话将花我几个月的时间。我最小的目标是:通过缺乏异常安全的例子来展示怎么使它们变得安全,并激励你在此专题之外去看和学更多的东西。

 

1.1     构造函数

    如果一个普通的成员函数

x.f()

抛出一个异常,你可以容忍此异常并试图再次调用它:

X x;

bool done;

do

   {

   try

      {

      done = true;

      x.f();

      }

   catch (...)

      {

      // do something to recover, then retry

      done = false;

      }

   }

while (!done);

    但,如果你试图再次调用一个构造函数,你实际上是调用了一个完全不同的对象:

bool done(false);

while (!done)

   {

   try

      {

      done = true;

      X x; // calls X::X()

      }

   // from this point forward, `x` does not exist

   catch (...)

      {

      // do something to recover, then retry

      done = false;

      }

   }

    你不能挽救一个构造函数抛异常的对象;异常的存在表明那个对象已经死了。

    当一个构造函数抛异常时,它杀死了其宿主对象而没有调用析构函数。这样的抛异常行为危害了“行为安全”:如果这个抛异常的构造函数分配了资源,你无法依赖析构函数释放它们。一般构造和析构是成对的,并期待后者清理前者。如果析构函数没有被调用,这个期望是不满足的。

    最后,如果你从构造函数中抛了一个异常,并且你的类是用户类的一个基类或子对象,那么用户类的构造函数必须处理你抛出的异常。或者它将异常抛给另外一个用户类的构造函数,如此递推下去,直到程序调用terminate()。实际上用户必须做你没有做的工作(维持构造函数的安全性)。

 

1.2     关于取舍的问题

    构造函数抛异常同时降低了接口安全和行为安全。除非有迫不得以的理由,不要让构造函数抛异常。

    也有不同的意见认为:异常应该被本来就做这事的专门代码捕获的。那些只是静静地接收异常而没有处理它们的异常处理函数违背了这些异常的初衷。如果一个函数没有准备好正确地处理一个异常,它应该将这个异常传递下去。

    最低事实是:必须有人处理异常;如果所有人都放过它,程序将终止。还必须同时捕获触发异常的条件;如果没人标记它,程序可能以任何方式终止,并且恐怕不怎么文雅。

    一个异常对象警示我们存在一个不该忽略的错误状况。不幸的是,这个对象的存在可能导致一个全新的不同的错误状况。在设计异常安全的时候,你必须在两个有时冲突的设计原则间进行取舍。

1.在错误发生时进行通报

2.防止这个通报行为导致其它错误。

    因为构造函数抛异常可能有有害的副作用,你必须小心权衡这两个原则。我不允许我写的构造函数中抛异常,这样设计倾向于原则2;但我不想将它推荐为普遍原则,在其它情况下这两个原则是等重的。自己好自判断吧。

 

1.3     析构函数

    析构函数抛异常可能使程序有奇怪的反应。它可能彻底地杀死程序。根据C++标准(subclause 15.1.1,“the terminate() function” ),简述如下:

    在某些情况下,异常处理必须被抛弃以减少一些微妙的错误。这些情况中包括:当因为异常而退栈过程中将要被析构的对象的析构函数。在这些情况下,函数void terminate()被调用。退栈不会完成。

    简而言之,析构函数不该提示是否发生了异常。但,如我上次所说,新的C++标准运行库程序uncaught_exception()可以让析构函数确定其所处的异常环境。不幸的是,我上次也说了,Visual C++未能正确地支持这个函数。

    问题比我提示的还要糟。我上次写到,Microsoftuncaught_exception()函数版本一定返回false,所以Visaul C++总告诉你的析构函数当前没有发生异常,在其中抛异常是可以的。如果你从一个支持uncaught_exception的环境转到Visual C++,以前正常工作的代码可能开始调用terminate()了。

 

    要尝试一下的话,试下面的例子:

#include <exception>

#include <stdio.h>

#include <stdlib.h>

 

using namespace std;

 

static void my_terminate_handler(void)

   {

   printf("Library lied; I'm in the terminate handler./n");

   abort();

   }

 

class X

   {

public:

   ~X()

      {

      if (uncaught_exception())

         printf("Library says not to throw./n");

      else

         {

         printf("Library says I'm OK to throw./n");

         throw 0;

         }

      }

   };

 

int main()

   {

   set_terminate(my_terminate_handler);

   try

      {

      X x;

      throw 0;

      }

   catch (...)

      {

      }

   printf("Exiting normally./n");

   return 0;

   }

 

    C++标准兼容的环境下,你得到:

Library says not to throw.

Exiting normally.

 

    Visual C++下,你得到:

Library says I'm OK to throw.

Library lied; I'm in the terminate handler.

并跟随一个程序异常终止。

 

And with six you get egg roll.

    建议:除非你确切知道你现在及以后所用的平台都正确支持uncaught_exception(),不要调用它。

   

1.4     部分删除

    即使你知道当前不在处理异常,你仍然不应该在析构函数中抛异常。考虑如下的例子:

class X

   {

public:

   ~X()

      {

      throw 0;

      }

   };

 

int main()

   {

   X *x = new X;

   delete x;

   return 0;

   }

 

    main()执行到delete x,如下两步将依次发生:

x的析构函数被调用。

operator delete被调用了来释放x的内存空间。

    但因为x的析构函数抛了异常,operator delete没有被调用。这危及了行为安全。如果还不信,试一下这个更完整的例子:

 

#include <stdio.h>

#include <stdlib.h>

 

class X

   {

public:

   ~X()

      {

      printf("destructor/n");

      throw 0;

      }

   void *operator new(size_t n) throw()

      {

      printf("new/n");

      return malloc(n);

      }

   void operator delete(void *p) throw()

      {

      printf("delete/n");

      if (p != NULL)

         free(p);

      }

   };

 

int main()

   {

   X *x = new X;

   try

      {

      delete x;

      }

   catch (...)

      {

      printf("catch/n");

      }

   return 0;

   }

 

    如果析构函数没有抛异常,程序输出:

new

destructor

delete

 

    实际上程序输出:

new

destructor

catch

 

    operator delete没有进入,x的内存空间没有被释放,程序有资源泄漏,the press hammers your product for eating memory, and you go back to flipping burgers for a living

    原则:异常安全要求你不能在析构函数中抛异常。和在构造函数抛异常上有不同意见不一样,这条是绝对的。为了明确表明意图,应该在申明析构函数时加上异常规格申明throw()

 

1.5     预告

    我本准备覆盖模板安全的,但没地方了。我将留到下次介绍,并开出推荐读物表。

C++异常处理示例

这两天我写了一个测试c++异常处理机制的例子,感觉有很好的示范作用,在此贴出来,给c++异常处理的初学者入门。本文后附有c++异常的知识普及,有兴趣者也可以看看。      下面的代码直接贴到你...
  • loveRooney
  • loveRooney
  • 2014年08月08日 14:49
  • 830

C++中异常处理中的异常重新抛出的一种用法

本文讨论了C++异常处理中重复抛出异常的一种用法
  • u010857719
  • u010857719
  • 2016年09月08日 21:56
  • 762

Android NDK之JNI异常处理

处理本机代码中的异常      为了处理以Java代码实现的方法执行中抛出的异常,或者是以本机代码编写的方法抛出的Java异常,JNI提供了Java异常机制的钩子程序。该机制与C/C++中常规函...
  • u013378580
  • u013378580
  • 2016年08月25日 16:24
  • 636

C++异常处理类与自定义异常处理类

转自:http://blog.csdn.net/makenothing/article/details/43273137 例1:自定义一个继承自excepton的异常类myExcep...
  • u014805066
  • u014805066
  • 2017年03月29日 17:32
  • 544

c++异常处理机制示例及讲解

原文链接:http://ticktick.blog.51cto.com/823160/191881 下面的代码直接贴到你的console工程中,可以运行调试看看效果,并分析c++的异常机制。 ...
  • u013727453
  • u013727453
  • 2015年04月24日 15:30
  • 1746

深入理解C++中的异常处理机制

异常处理 增强错误恢复能力是提高代码健壮性的最有力的途径之一,C语言中采用的错误处理方法被认为是紧耦合的,函数的使用者必须在非常靠近函数调用的地方编写错误处理代码,这样会使得其变得笨拙和难以使用。C...
  • u013982161
  • u013982161
  • 2016年11月06日 12:35
  • 826

【VS开发】C++异常处理操作

异常处理的基本思想是简化程序的错误代码,为程序键壮性提供一个标准检测机制。 也许我们已经使用过异常,但是你会是一种习惯吗,不要老是想着当我打开一个文件的时候才用异常判断一下,我知道对你来说你喜欢...
  • LG1259156776
  • LG1259156776
  • 2016年05月21日 21:34
  • 1669

基于TCP传输的网络编程异常处理

 基于TCP传输的网络编程异常处理 一:进程一端退出(exit,CTRL+C,挂掉)(跟主动CLOSE、主动关机一样)  内核会关闭所有句柄触发FIN分节发送(但如果设置了SO_LINGER...
  • doitsjz
  • doitsjz
  • 2017年03月11日 14:15
  • 624

linux C 异常处理的方式

目前遇到这样的问题,大概在2000多台服务器里面有100多多台一个c进程挂掉了,由于公司各种的流程调试起来非常困难。 这几天google了下找到了一些资料,捕获异常堆栈的,如http://spin....
  • wangyin159
  • wangyin159
  • 2015年07月23日 21:02
  • 604

C++/MFC全局未知异常捕获并进行调试

C++/MFC全局未知异常捕获Dump出来并进行调试全局捕获未知异常函数名: WINBASEAPI LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Set...
  • KellyGod
  • KellyGod
  • 2017年04月01日 00:25
  • 1389
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C与C++中的异常处理13
举报原因:
原因补充:

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