构造函数中抛出的异常

来源:互联网 发布:传智播客java基础班 编辑:程序博客网 时间:2024/05/16 11:54

http://se.csai.cn/ExpertEyes/No143.htm

构造函数中抛出的异常

标准C++中定义构造函数是一个对象构建自己,分配所需资源的地方,一旦构造函数执行完毕,则表明这个对象已经诞生了,有自己的行为和内部的运行状态,之后还有对象的消亡过程(析构函数的执行)。可谁能保证对象的构造过程一定能成功呢?说不定系统当前的某个资源不够,导致对象不能完全构建好自己。


对象总是由不断的继承或不断的聚合而来,对象的构造过程实际上是这些所有的子对象按规定顺序的构造过程,其中这些过程中的任何一个子对象在构造时发生异常,对象都不能说自己完成了全部的构造过程,这种情况称为对象的部分构造.因此这里就有一个棘手的问题,当发生对象的部分构造时,对象将析构吗?如果时,又将如何析构呢?

  从运行结果可以得出如下结论:
  (1) 对象的部分构造是很常见的,异常的发生点也完全是随机的,程序员要谨慎处理这种情况;
  (2) 当对象发生部分构造时,已经构造完毕的子对象将会逆序地被析构(即异常发生点前面的对象);而还没有开始构建的子对象将不会被构造了(即异常发生点后面的对象),当然它也就没有析构过程了;还有正在构建的子对象和对象自己本身将停止继续构建(即出现异常的对象),并且它的析构是不会被执行的。

  构造函数中抛出异常时概括性总结
  (1) C++中通知对象构造失败的唯一方法那就是在构造函数中抛出异常;
  (2) 构造函数中抛出异常将导致对象的析构函数不被执行;
  (3) 当对象发生部分构造时,已经构造完毕的子对象将会逆序地被析构;


另一种做法,将对资源的申请,封装到Init函数中.

创建完对象后Init.

但有一个缺点,用户必须记得在其它调用前,手动调用Init.


析构函数中是不永许抛出异常的,而且在C++标准中也特别声明到了这一点。

    C++异常处理模型是为C++语言量身设计的,更进一步的说,它实际上也是为C++语言中面向对象而服务的,我们在前面的文章中多次不厌其烦的声明到,C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。好的,既然如此!那么如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源,这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。不知大家是否明白了这段话所蕴含的真正内在涵义没有,那就是上面的论述C++异常处理模型它其实是有一个前提假设——析构函数中是不应该再有异常抛出的。试想!如果对象出了异常,现在异常处理模块为了维护系统对象数据的一致性,避免资源泄漏,有责任释放这个对象的资源,调用对象的析构函数,可现在假如析构过程又再出现异常,那么请问由谁来保证这个对象的资源释放呢?而且这新出现的异常又由谁来处理呢?不要忘记前面的一个异常目前都还没有处理结束,因此这就陷入了一个矛盾之中,或者说无限的递归嵌套之中。所以C++标准就做出了这种假设,当然这种假设也是完全合理的,在对象的构造过程中,或许由于系统资源有限而致使对象需要的资源无法得到满足,从而导致异常的出现,但析构函数完全是可以做得到避免异常的发生,毕竟你是在释放资源呀!


            系统非常复杂,不可能保证所有的程序员写出的程序完全没有bug。因此杜绝在析构函数中决不发生任何异常的这种保证确实是有点理想化了。那么当无法保证在析构函数中不发生异常时,该怎么办?我们不能眼睁睁地看着系统崩溃呀!

  其实还是有很好办法来解决的。那就是把异常完全封装在析构函数内部,决不让异常抛出函数之外。这是一种非常简单,也非常有效的方法。按这种方法把上面的程序再做一点改动,那么程序将避免了崩溃的厄运。如下:

class MyTest_Base
{
public:
virtual ~ MyTest_Base () 
{
cout << "开始准备销毁一个MyTest_Base类型的对象"<< endl;

// 一点小的改动。把异常完全封装在析构函数内部
try
{
// 注意:在析构函数中抛出了异常
throw std::exception("在析构函数中故意抛出一个异常,测试!");
}
catch(…) {}

}

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

  (1) C++中析构函数的执行不应该抛出异常;
  (2) 假如析构函数中抛出了异常,那么你的系统将变得非常危险,也许很长时间什么错误也不会发生;但也许你的系统有时就会莫名奇妙地崩溃而退出了,而且什么迹象也没有,崩得你满地找牙也很难发现问题究竟出现在什么地方;
  (3) 当在某一个析构函数中会有一些可能(哪怕是一点点可能)发生异常时,那么就必须要把这种可能发生的异常完全封装在析构函数内部,决不能让它抛出函数之外.




其实很多程序员朋友受到过太多这种类似的冤枉,例如一个程序原来运行挺好的,以后进行功能扩充后,程序却时常出现崩溃现象。其实有时程序扩充时也没添加多少代码,而且相关程序员也很认真仔细检查自己添加的代码,确认后来添加的代码确实没什么问题呀!可相关的负责人也许不这么认为,觉得程序以前一直运行挺好的,经过你这一番修改之后就出错了,能不是你添加的代码所导致的问题吗?真是程序开发领域的窦娥冤呀!其实这种推理完全是没有根据和理由的,客观公正一点地说,程序的崩溃与后来添加的模块代码肯定是会有一定的相关性!但真正的bug也许就在原来的系统中一直存在,只不过以前一直没诱发表现出来而已!