Quantcast
Channel: 程序人生 »代码疯子
Viewing all articles
Browse latest Browse all 59

C++中的异常与栈展开

$
0
0

在《More Effective C++》一书中提到:

两种情况下destructor会被调用。第一种情况是当对象在正常状态下被销毁,也就是当它离开了它的生存空间(scope)或是被明确的删除;第二种情况是当对象被exception处理机制——也就是exception传播过程中的stack-unwinding(栈展开)机制——销毁。

那什么是栈展开(stack-unwinding)机制呢?在C++异常机制中,代码控制流会从throw语句跳转到第一个可以处理这种异常的catch语句的地方,在达到这样的catch语句时,所有位于引发异常和这个catch语句之间的作用域范围内的(即能够处理这个异常的try-catch结构中try起始处到引发异常的throw语句之间)已经构造好的局部变量都会被销毁(如果是对象,对应的析构函数就会被调用),这个过程就是栈展开。

代码详细的执行过程:

  1. 执行流进行try块之后开始执行代码;
  2. 如果没有异常产生,那么try对应的catch结构中的代码不会被执行,执行流跳转换到try-catch之后的代码开始执行;
  3. 如果在执行try块结构内部的代码时抛出异常(通过throw语句,注意这里会产生copy constructor的调用,具体看后面描述);这时候系统会去寻找一个可以捕获该异常的catch语句,这个过程是一层一层往主调函数回溯的;
  4. 如果到了最外层的try块结构仍然没能找到能够处理这个异常的catch结构,terminate将被调用;
  5. 如果找到匹配的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) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


Viewing all articles
Browse latest Browse all 59

Trending Articles