栈展开(stack unwinding)的定义
抛出异常时,将暂停当前函数的执行,开始查找匹配的
catch
子句。首先检查 throw
本身是否在 try
块内部,如果是,检查与该 try
相关的 catch
子句,看是否可以处理该异常。如果不能处理,就退出当前函数,并且释放当前函数的内存并销毁局部对象,继续到上层的调用函数中查找,直到找到一个可以处理该异常的 catch
。这个过程称为栈展开(stack unwinding)。当处理该异常的 catch
结束之后,紧接着该 catch
之后的点继续执行。 -
为局部对象调用析构函数
在栈展开的过程中,会释放局部对象所占用的内存并运行类类型局部对象的析构函数。但需要注意的是,如果一个块通过
动态分配内存,并且在释放该资源之前发生异常,该块因异常而退出,那么在栈展开期间不会释放该资源,编译器不会删除该指针,这样就会造成内存泄露。new
-
析构函数应该从不抛出异常
在为某个异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常,将会导致调用标准库
函数。通常terminate
函数将调用terminate
函数,导致程序的非正常退出。所以析构函数应该从不抛出异常。abort
-
异常与构造函数
如果在构造函数对象时发生异常,此时该对象可能只是被部分构造,要保证能够适当的撤销这些已构造的成员。
-
未捕获的异常将会终止程序
不能不处理异常。如果找不到匹配的catch,程序就会调用库函数
。terminate
例子
#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 Function C" << endl;
d.MyName = " C";
throw MyException();
cout << "Exiting Function C" << endl;
}
void B(Dummy d, int i) {
cout << "Entering Function B" << endl;
d.MyName = " B";
C(d, i + 1);
cout << "Exiting Function B" << endl;
}
void A(Dummy d, int i) {
cout << "Entering Function A" << 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;
return 0;
}
/*
*/
进行编译,运行,可得到如下结果:
$ g++ stack_unwinding.cpp -o stack_test -std=c++11
$ ./stack_test
Entering main
Created Dummy: M
Copy created Dummy: M
Entering Function A
Copy created Dummy: A
Entering Function B
Copy created Dummy: B
Entering Function C
Destroyed Dummy: C
Destroyed Dummy: B
Destroyed Dummy: A
Destroyed Dummy: M
Caught an exception of type: 11MyException
Exiting main.
程序运行时对应栈的内容如下图所示:

- 根据创建
对象的顺序,在它们超出范围时将其销毁。Dummy
- 除了包含 catch 语句的
之外,其他函数均未完成。main
- 函数
绝不会从其对A
的调用返回,并且B()
B
的调用返回。C()
reference