看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它了。