13.以引用方式捕获异常

前言

 
异常有三种方式传递到catch子句:通过指针(by pointer),通过传值(by value)或通过引用(by reference)。


by pointer

 
用指针来传递异常理论上来说是效率最高的,因为只有它不用copy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class exception { ... };
void someFunction(){
static exception ex; // 异常对象
...
throw &ex; // 抛出一个指针,指向 ex
...
}
void doSomething(){
try {
someFunction(); // 抛出一个 exception*
}
catch (exception *ex) { // 捕获 exception*;
... // 没有对象被拷贝
}
}

逻辑上看起来没错,但老是有人忘了下述约定:定义异常对象时,必须确保程序控制权离开抛出指针的函数后对象仍然继续生存,否则catch的对象已被析构。具体来说,当我们用指针抛出异常,必须确保该异常是一个static对象或者位于heap中。
但这两种情况又引入了新的问题:你不知道该在何时删除它们。中的对象不删除必然会导致资源泄漏,而全局变量无法删除,否则程序行为将不可预测。
c++不鼓励通过指针传递异常,四大标准异常:bad_alloc、bad_cast(dynamic_cast失败)、bad_typeid(dynamic_cast操作空指针)、bad_exception(unexpected异常)均不是指向对象的指针,因此你必须通过值或引用来捕获。


by value

 
值捕获有两大缺陷:

  1. 两次拷贝
  2. slicing problem(由于拷贝静态类型引起)
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
class exception {
public:
virtual const char * what() throw();
};
class runtime_error:public exception { ... };
class Validation_error:public runtime_error {
public:
virtual const char * what() throw();
...
};
void someFunction(){
...
if (a validation 测试失败) {
throw Validation_error();
}
...
}
void doSomething(){
try {
someFunction();
}
catch (exception ex) {
cerr << ex.what(); // 调用 exception::what(),而不是 Validation_error::what()
...
}
}

传值模式在何时都只会调用基类函数,完全丧失了多态性质。


by reference

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void someFunction(){
...
if (a validation 测试失败) {
throw Validation_error();
}
...
}
void doSomething(){
try {
someFunction();
}
catch (exception& ex) {//引用捕获
cerr << ex.what(); //Validation_error::what(),
...
}
}


总结

 
如果你通过引用捕获异常(catch by reference),有优点如下:

  1. 无需担心对象已被析构或者不确定对象何时需要删除(by pointer缺点)
  2. 无需拷贝2次且丧失多态性质(by value缺点)