当你写一个 catch 子句时,必须确定让异常通过何种方式传递到 catch 子句里。你可以 有三个选择:与你给函数传递参数一样,通过指针(by pointer),通过传值(by value) 或通过引用(by reference)。 我们首先讨论通过指针方式捕获异常(catch by pointer)。从 throw 处传递一个异常 到 catch 子句是一个缓慢的过程,在理论上这种方法的实现对于这个过程来说是效率最高 的。因为在传递异常信息时,只有采用通过指针抛出异常的方法才能够做到不拷贝对象,例如: c class exception { ... }; void someFunction() { static exception ex; ... throw &ex; ... } void doSomething() { try { someFunction(); } catch (exception *ex) { ... } } 这看上去很不错,但是实际情况却不是这样。为了能让程序正常运行,程序员定义异常 对象时必须确保当程序控制权离开抛出指针的函数后,对象还能够继续生存。全局与静态对 象都能够做到这一点,但是程序员很容易忘记这个约束。如果真是如此的话,他们会这样写 代码: ```c void someFunction() { exception ex; … throw &ex; … }

 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

这简直糟糕透了,因为处理这个异常的 catch 子句接受到的指针,其指向的对象已经不
再存在。

另一种抛出指针的方法是建立一个堆对象(new heap object):

```c
void someFunction()
{
	...
	throw new exception;
	...
}
```

这避免了捕获一个指向已被释放对象的指针的问题,但是 catch 子句的作者又面临一个
令人头疼的问题:他们是否应该删除他们接受的指针?如果是在堆中建立的异常对象,那他 们必须删除它,否则会造成资源泄漏。如果不是在堆中建立的异常对象,他们绝对不能删除 它,否则程序的行为将不可预测。这是不可能知道的。


通过值捕获异常(catch-by-value)可以解决上述的问题,例如异常对象删除的问题和使用标准异常类型的问题。但是当它们被抛出时系统将对异常对象拷贝两次。而且它会产生 slicing problem,即派生类的异常对象被做为基类异常对象捕获时, 那它的派生类行为就被切掉了(sliced off)。这样的 sliced 对象实际上是一个基类对象: 它们没有派生类的数据成员,而且当本准备调用它们的虚拟函数时,系统解析后调用的却是 基类对象的函数。

最后剩下的方法就是通过引用捕获异常(catch-by-reference)。通过引用捕获异常能使 你避开上述所有问题。不像通过指针捕获异常,这种方法不会有对象删除的问题而且也能捕 获标准异常类型。也不象通过值捕获异常,这种方法没有 slicing problem,而且异常对象只被拷贝一次。

我们采用通过引用捕获异常的方法重写最后那个例子,如下所示:

```c
void someFunction()
{
	...
	if (a validation 测试失败) {
	    throw Validation_error();
	}
	... 
}

void doSomething()
{
  try {
	    someFunction();
  }
  catch (exception& ex) {
    
    cerr << ex.what();
    ...
	} 
}
```

如果你通过引用捕获异常(catch by reference),你就能避开上述所有问题,不会为 是否删除异常对象而烦恼;能够避开 slicing 异常对象;能够捕获标准异常类型;减少异常 对象需要被拷贝的数目。