1 提出问题
1 析构函数调用时机
析构函数会在下面两种情况下被调用:
1, 离开对象所在作用域,对象生命周期终结,析构函数被调用,对象被销毁。
2, 异常抛出引起了栈展开(stack-unwinding),离开对象的所在的作用域,对象生命周期中介,析构函数被调用。
第一种情况如下面代码所示,离开函数myfunction()后,对象myobject会自动被析构。
void myfunction() {
MyClass myobject;
}
2 栈展开
异常被抛出后,当前函数停止执行,并查找对应的catch字句。首先检查throw是否在try内,若在,则将该异常与try对应的catch子句进行匹配。若匹配失败,则释放当前函数内存,销毁局部对象,将异常抛向上层调用函数,直到找到可以匹配的catch子句。
上面的过程就是”栈展开“。当处理catch结束后,紧接着catch之后继续执行。但是,需要注意如下几点:
1,栈展开过程只能销毁局部对象,即调用局部对象的析构函数。
若使用new在heap声明了对象,且在delete调用前出现异常,由于提前退出,在heap中声明的资源将不会被释放,造成内存泄漏。
2,析构函数应该从不抛出异常
在进行栈展开过程中,析构函数若再次抛另一个异常,则会导致标准库函数terminate被调用。terminate函数调用abort函数,程序异常退出。
3,异常与构造函数
同上篇所述,构造函数调用过程中出现异常,应保证已经被构造的成员能被恰当销毁。
4,未捕获的异常将终止程序
同上面所述,库函数terminate被调用,终止程序运行。
3 问题
正如上面所提到,若析构函数抛出异常,有两种危害:
1,异常点之后的语句无法完成,析构工作没有完成。
2,有可能是栈展开调用析构函数,两次异常抛出导致程序终结。
2 解决问题
因此,最简单粗暴的方式,就是直接拦截析构函数中抛出的异常,保证不会有更多的异常向上传递。如下面代码所示。
~Destructor() {
try {
doSomething();
} catch (...) {
// doNothing, avoid more exception
}
}