在《More Effective C++》一书中提到:
两种情况下destructor会被调用。第一种情况是当对象在正常状态下被销毁,也就是当它离开了它的生存空间(scope)或是被明确的删除;第二种情况是当对象被exception处理机制——也就是exception传播过程中的stack-unwinding(栈展开)机制——销毁。
那什么是栈展开(stack-unwinding)机制呢?在C++异常机制中,代码控制流会从throw语句跳转到第一个可以处理这种异常的catch语句的地方,在达到这样的catch语句时,所有位于引发异常和这个catch语句之间的作用域范围内的(即能够处理这个异常的try-catch结构中try起始处到引发异常的throw语句之间)已经构造好的局部变量都会被销毁(如果是对象,对应的析构函数就会被调用),这个过程就是栈展开。
代码详细的执行过程:
- 执行流进行try块之后开始执行代码;
- 如果没有异常产生,那么try对应的catch结构中的代码不会被执行,执行流跳转换到try-catch之后的代码开始执行;
- 如果在执行try块结构内部的代码时抛出异常(通过throw语句,注意这里会产生copy constructor的调用,具体看后面描述);这时候系统会去寻找一个可以捕获该异常的catch语句,这个过程是一层一层往主调函数回溯的;
- 如果到了最外层的try块结构仍然没能找到能够处理这个异常的catch结构,terminate将被调用;
- 如果找到匹配的catch处理程序,在catch的形参初始化后,将进行栈展开的过程。;
在第3点中提到,通过throw抛出一个异常对象,会产生复制构造函数的调用,不管catch是以by value的方式还是以by reference的方式。因为有栈展开这个过程,已经构造好的局部变量都被销毁了,如果不通过copy constructor构造一个临时的异常对象,那么即便是reference,也会指向不存在的对象。即使throw的对象时static或者global,都会导致copy constructor的调用。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | #include <string> #include <iostream> using namespace std; class MyException{}; class Dummy { public: Dummy(string s) : MyName(s) { PrintMsg("Created Dummy:"); } Dummy(const Dummy& other) : MyName(other.MyName){ PrintMsg("Copy created Dummy:"); } ~Dummy(){ PrintMsg("Destroyed Dummy:"); } void PrintMsg(string s) { cout << s << MyName << endl; } string MyName; int level; }; void C(Dummy d, int i) { cout << "Entering FunctionC" << endl; d.MyName = " C"; throw MyException(); cout << "Exiting FunctionC" << endl; } void B(Dummy d, int i) { cout << "Entering FunctionB" << endl; d.MyName = "B"; C(d, i + 1); cout << "Exiting FunctionB" << endl; } void A(Dummy d, int i) { cout << "Entering FunctionA" << endl; d.MyName = " A" ; // Dummy* pd = new Dummy("new Dummy"); //Not exception safe!!! B(d, i + 1); // delete pd; cout << "Exiting FunctionA" << endl; } int main() { cout << "Entering main" << endl; try { Dummy d(" M"); A(d,1); } catch (MyException& e) { cout << "Caught an exception of type: " << typeid(e).name() << endl; } cout << "Exiting main." << endl; char c; cin >> c; } /* Output: Entering main Created Dummy: M Copy created Dummy: M Entering FunctionA Copy created Dummy: A Entering FunctionB Copy created Dummy: B Entering FunctionC Destroyed Dummy: C Destroyed Dummy: B Destroyed Dummy: A Destroyed Dummy: M Caught an exception of type: class MyException Exiting main. */ |
注意一点:不要让异常逃离析构函数。因为异构函数执行的时候,可能正好已经发生了异常,这时候正在栈展开的过程中,如果析构函数再抛出一个异常,系统中由于同时存在两个未处理的异常,terminate将会被调用;此外,异常逃离析构函数中,意味着异常发生的地方之后的代码没有被执行,此时析构函数也没有完成它全部的使命。
本文整合自以下资料:
《More Effective C++》
Exceptions and Stack Unwinding in C++
本文地址: 程序人生 >> C++中的异常与栈展开
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!