构造函数、析构函数抛出异常的问题

1. 抛出异常

1.1 抛出异常(也称为抛弃异常)即检测是否产生异常,在C++中,其采用throw语句来实现,如果检测到产生异常,则抛出异常。

该语句的格式为: throw 表达式;

如果在try语句块的程序段中(包括在其中调用的函数)发现了异常,且抛弃了该异常,则这个异常就可以被try语句块后的某个catch语句所捕获并处理,捕获和处理的条件是被抛弃的异常的类型与catch语句的异常类型相匹配。由于C++使用数据类型来区分不同的异常,因此在判断异常时,throw语句中的表达式的值就没有实际意义,而表达式的类型就特别重要。  

1.2 抛出异常实际是作为另一种返回值来使用的。 抛出异常的好处一是可以不干扰正常的返回值,另一个是调用者必须处理异常,而不像以前c语言返回一个整数型的错误码,调用者往往将它忽略了。

1.3 举例说明

假如说A方法掉调用-->B方法调用-->C方法。 然后在B和C方法里定义了throws Exception。A方法里定义了Try Catch。

那么调用A方法时,在执行到C方法里出现了异常,那么这个异常就会从C抛到B,再从B抛到A。在A里的try catch就会捕获这个异常,然后你就可以在catch写自己的处理代码。

那么为什么当时出现了异常不去处理呢? 因为你业务逻辑调用的是A方法,你执行了A方法,当然要在A里得到异常,然后来处理。如果在C里面就处理异常,这就破坏程序结构了。 另外,A调用了C方法,假如还接着也调用了D,E,F方法,假如他们都有可能抛出异常,你说是在A里面获得处理一次好,还是在C,D,E,F得到了异常,每个都当时处理一下的好? 当时就处理异常理论上也是可以的,而且大多数时候,到底在哪处理异常,是要根据需求和项目的具体情况的。

2. 构造函数可以抛出异常。

3. C++标准指明析构函数不能、也不应该抛出异常。

C++异常处理模型是为C++语言量身设计的,更进一步的说,它实际上也是为C++语言中面向对象而服务的。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分

上面的论述C++异常处理模型它其实是有一个前提假设——析构函数中是不应该再有异常抛出的。试想,如果对象出了异常,现在异常处理模块为了维护系统对象数据的一致性,避免资源泄漏,有责任释放这个对象的资源,调用对象的析构函数,可现在假如析构过程又再出现异常,那么请问由谁来保证这个对象的资源释放呢?而且这新出现的异常又由谁来处理呢?不要忘记前面的一个异常目前都还没有处理结束,因此这就陷入了一个矛盾之中,或者说无限的递归嵌套之中。所以C++标准就做出了这种假设,当然这种假设也是完全合理的,在对象的构造过程中,或许由于系统资源有限而致使对象需要的资源无法得到满足,从而导致异常的出现,但析构函数完全是可以做得到避免异常的发生,毕竟你是在释放资源呀!

假如无法保证在析构函数中不发生异常,怎么办? 虽然C++标准中假定了析构函数中不应该,也不永许抛出异常的。但实际的软件系统开发中是很难保证到这一点的。所有的析构函数的执行过程完全不发生一点异常,这根本就是天方夜谭,或者说自己欺骗自己算了。而且有时候析构一个对象(释放资源)比构造一个对象还更容易发生异常,例如一个表示引用记数的句柄不小心出错, 结果导致资源重复释放而发生异常,当然这种错误大多时候是由于程序员所设计的算法在逻辑上有些小问题所导致的,但不要忘记现在的系统非常复杂,不可能保证 所有的程序员写出的程序完全没有bug。因此杜绝在析构函数中决不发生任何异常的这种保证确实是有点理想化了。

3.1 more effective c++提出两点理由(析构函数不能抛出异常的理由):

1)如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。

2)通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。

3.2 那么当无法保证在析构函数中不发生异常时, 该怎么办?

其实还是有很好办法来解决的。那就是把异常完全封装在析构函数内部,决不让异常抛出函数之外。这是一种非常简单,也非常有效的方法。  

~ClassName()

{

  try{

      do_something();

  }

  catch(){  //这里可以什么都不做,只是保证catch块的程序抛出的异常不会被扔出析构函数之外

   }

}

3.3 析构函数中抛出异常时概括性总结

1)C++中析构函数的执行不应该抛出异常;

2)假如析构函数中抛出了异常,那么你的系统将变得非常危险,也许很长时间什么错误也不会发生;但也许你的系统有时就会莫名奇妙地崩溃而退出了,而且什么迹象也没有,崩得你满地找牙也很难发现问题究竟出现在什么地方;

3)当在某一个析构函数中会有一些可能(哪怕是一点点可能)发生异常时,那么就必须要把这种可能发生的异常完全封装在析构函数内部,决不能让它抛出函数之外(这招简直是绝杀!呵呵!);

4)一定要切记上面这几条总结,析构函数中抛出异常导致程序不明原因的崩溃是许多系统的致命内伤! 

原文地址:https://www.cnblogs.com/fly1988happy/p/2442765.html