Cpp中throw是怎么实现的?

看Cpp Primer第五版,里面提到说catch子句的异常声明也可以采用左值引用。一开始我看得不够仔细,一下就想着,和函数参数列表应该差不多,修改后再次抛出该对象就还是原来的,改变也会被记录。

#include <iostream>
#include <vector>

using namespace std;

struct t
{
    int i = 0;
    virtual void f(){cout << "t" << endl;}
};

struct s : t
{
    void f() override {cout << "s" << endl;}
};

int main()
{
    s ii = s();
    try
    {
        cout << &ii << endl;
        throw ii;
    }
    catch(s &iii)
    {
        cout << &iii << endl;
        try
        {
            throw;
        }
        catch(s &iiii)
        {
            cout << &iiii << endl;
        }
    }
}

输出:
0x7ffff7a96e10
0xc24ca0
0xc24ca0

结果发现后两个倒是一样,那前面的第一个呢?说好的左值引用呢?于是就测了下虚函数起没起作用。

#include <iostream>
#include <vector>

using namespace std;

struct t
{
    int i = 0;
    virtual void f(){cout << "t" << endl;}
};

struct s : t
{
    void f() override {cout << "s" << endl;}
};

int main()
{
    s ii = s();
    try
    {
        cout << &ii << endl;
        throw ii;
    }
    catch(t &iii)
    {
        iii.f();
        cout << &iii << endl;
        try
        {
            throw;
        }
        catch(t iiii)
        {
            iiii.f();
            cout << &iiii << endl;
        }
    }
}

输出:
0x7fffcaddb9a0
s
0x826ca0
t
0x7fffcaddb990

这么看来虚函数还是起到它的作用了的。但是为什么地址不相等呢?于是就查了下,发现知乎上有人回答,虽然问的是为什么不能是右值引用,但提到了C++标准,于是又去找标准。在ISO/IEC JTC1 SC22 WG21 N3690的Page388页可以发现下面这段话。

3 Throwing an exception copy-initializes (8.5, 12.8) a temporary object, called the exception object. The temporary is an lvalue and is used to initialize the variable named in the matching handler (15.3).

也就是说其实在throw的时候,Cpp先拷贝初始化了一个临时对象,再把这临时对象传给异常声明里面的参数。那么显然是不会和原来那个对象地址相同的。

4 The memory for the exception object is allocated in an unspecified way, except as noted in 3.7.4.1. If a handler exits by rethrowing, control is passed to another handler for the same exception.

然后如果再rethrow,抛出的还是原来那个的临时对象,因为此处说的只是把控制权转给了下一个handler。这就可以解释为什么最上面那程序后两个地址是一样的。同时也可以解释为什么不能是右值引用了。讲道理,如果是右值引用,你throw之后这个异常对象(临时对象)被移动给了catch里的参数,就已经是一个废呆呆兽对象了,处于可以析构的状态,你还怎么确保使用它的安全性?就别提rethrow它了。

参考资料

  1. 为什么catch的declaration 不能使用 rvalue reference?
  2. ISO/IEC JTC1 SC22 WG21 N3690