构造函数和析构函数分别管理对象的建立和释放,负责对象的诞生和死亡的过程。当一个对象诞生时,构造函数负责创建并初始化对象的内部环境,包括分配内存、创建内部对象和打开相关的外部资源,等等。而当对象死亡时,析构函数负责关闭资源、释放内部的对象和已分配的内存。
在对象生死攸关的地方,如果程序代码出现问题,常常会发生内存泄漏,从而产生可能危害系统运行的孤魂野鬼。大量的事实表明,业务逻辑代码写得非常严谨的程序在运行中仍然发现存在内存泄露,大都是构造和析构部分的代码存在问题。
而许多程序员都习惯于面向对象的编程,需要时就建立一个对象,不用时就将其释放。这样的习惯简化了我们的思路,正是面向对象编程思想带来的好处。也许由于太习惯了,很多程序员都忽略了在对象生死的瞬间也可能产生异常的问题,这种现象却值得我们去认真反思。
其实,对象生死间的异常问题是一个充满争议的问题。甚至不同的编程语言,在对象生死间的异常问题上也持不同的态度。
C++语言说:一个对象在出生的过程中发生异常问题,那这个对象就是一个没有生命的怪胎。既然它不是一个完整的对象,就根本不存在析构或释放的说法。因此,C++在执行构造函数过程中产生异常时,是不会调用对象的析构函数的,而仅仅清理和释放产生异常前的那些C++管理的变量空间等,之后就把异常抛给程序员处理。
那么关于析构函数中的异常又会怎样呢?
对象在死亡的过程中发生异常又引出一个有趣的问题,“想死死不了”或者“死了一半又不能死了”!那么,这个对象到底是死了还是活着?这种既死又活的对象,就像量子理论中的那只“薛定谔的猫”一样有趣。的确存在,却难以琢磨!
为此,C++根本不去纠缠这种复杂的问题,而是采用最简单的办法:如果析构函数抛出异常,将直接导致当前执行线程异常终止!如果是主线程中发生析构异常,程序立即退出!
C++的这一做法是可以理解的,当代码已经走进无法想通的死胡同,对象只能疯掉,从而毁灭整个程序世界!看来并非芸芸众生才有无法逃脱的苦海,其实运行中的程序进程也有解不开的心结!
所以,“永远不要在析构函数中抛出异常”成了编写C++代码的一条铁律!
构造函数和析构函数中的异常
1、构造函数可以抛出异常。
2、c++标准指明析构函数不能、也不应该抛出异常。
more effective c++关于第2点提出两点理由:
1)如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
2)通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
解决办法:
1)永远不要在析构函数抛出异常。
2)通常第一点有时候不能保证。可以采取如下的方法:
~ClassName() { try{ do_something(); } catch( ){ // 这里可以什么都不做,只是保证catch块的程序抛出的异常不会被扔出析构函数之外。 } }
小结
构造函数抛出异常后不会调用析构函数
析构函数中抛出异常时概括性总结
(1) C++中析构函数的履行不应当抛出异常;
(2) 假如析构函数中抛出了异常,那么你的体系将变得非常危险,也许很长时光什么错误也不会产生;但也许你的体系有时就会莫名奇妙地崩溃而退出了,而且什么迹象也没有,崩得你满地找牙也很难发明问题毕竟呈现在什么处所;
(3) 当在某一个析构函数中会有一些可能产生异常时,那么就必须要把这种可能产生的异常完全封装在析构函数内部,决不能让它抛出函数之外;
(4) 必定要切记上面这几条总结,析构函数中抛出异常导致程序不明原因的崩溃是许多体系的致命内伤!