副标题#e#
当你写一个catch子句时,必需确定让异常通过何种方法通报到catch子句里。你可以有三个选择:与你给函数通报参数一样,通过指针(by pointer),通过传值(by value)或通过引用(by reference)。
我们首先接头通过指针方法捕捉异常(catch by pointer)。从throw处通报一个异常到catch子句是一个迟钝的进程,在理论上这种要领的实现对付这个进程来说是效率最高的。因为在通报异常信息时,只有回收通过指针抛出异常的要领才气够做到不拷贝工具,譬喻:
class exception { ... }; // 来自尺度C++库(STL)
// 中的异常类条理
void someFunction()
{
static exception ex; // 异常工具
...
throw &ex; // 抛出一个指针,指向ex
...
}
void doSomething()
{
try {
someFunction(); // 抛出一个 exception*
}
catch (exception *ex) { // 捕捉 exception*;
... // 没有工具被拷贝
}
}
这看上去很不错,可是实际环境却不是这样。为了能让措施正常运行,措施员界说异常工具时必需确保当措施节制权分开抛出指针的函数后,工具还可以或许继承保留。全局与静态工具都可以或许做到这一点,可是措施员很容易健忘这个约束。假如然是如此的话,他们会这样写代码:
void someFunction()
{
exception ex; // 局部异常工具;
// 当退出函数的保留空间时
// 这个工具将被释放。
...
throw &ex; // 抛出一个指针,指向
... // 已被释放的工具
}
这的确糟糕透了,因为处理惩罚这个异常的catch子句接管到的指针,其指向的工具已经不再存在。
#p#副标题#e#
另一种抛出指针的要领是在成立一个堆工具(new heap object):
void someFunction()
{
...
throw new exception; // 抛出一个指针,指向一个在堆中
... // 成立的工具(但愿
}
// 本身不要再抛出一个
// 异常!)
这制止了捕捉一个指向已被释放工具的指针的问题,可是catch子句的作者又面对一个令人头疼的问题:他们是否应该删除他们接管的指针?假如是在堆中成立的异常工具,那他们必需删除它,不然会造成资源泄漏。假如不是在堆中成立的异常工具,他们绝对不能删除它,不然措施的行为将不行预测。该如何做呢?
这是不行能知道的。一些clients大概会通报全局或静态工具的地点,另一些大概转递堆中成立的异常工具的地点。通过指针捕捉异常,将碰着一个哈姆雷特式的困难:是删除照旧不删除?这是一个难以答复的问题。所以你最好避开它。
并且,通过指针捕捉异常也不切合C++语言自己的类型。四个尺度的异常――bad_alloc(当operator new(拜见条款8)不能分派足够的内存时,被抛出),bad_cast(当dynamic_cast针对一个引用(reference)操纵失败时,被抛出),bad_typeid(当dynamic_cast对空指针举办操纵时,被抛出)和bad_exception(用于unexpected异常;拜见条款14)――都不是指向工具的指针,所以你必需通过值或引用来捕捉它们。
通过值捕捉异常(catch-by-value)可以办理上述的问题,譬喻异常工具删除的问题和利用尺度异常范例的问题。可是当它们被抛出时系统将对异常工具拷贝两次(拜见条款12)。并且它会发生slicing problem,即派生类的异常工具被做为基类异常工具捕捉时,那它的派生类行为就被切掉了(sliced off)。这样的sliced工具实际上是一个基类工具:它们没有派生类的数据成员,并且当挪用它们的虚拟函数时,系统理会后挪用的是基类工具的函数。(当一个工具通过传值方法通报给函数,也会产生一样的环境――拜见Effective C++ 条款22)。譬喻下面这个措施回收了扩展自尺度异常类的异常类条理体系:
class exception { // 如上,这是
public: // 一个尺度异常类
virtual const char * what() throw();
// 返回异常的简短描写.
... // (在函数声明的末了处
// 的"throw()",
}; //有关它的信息
class runtime_error: //也来自尺度C++异常类
public exception { ... };
class Validation_error: // 客户本身插手个类
public runtime_error {
public:
virtual const char * what() throw();
// 从头界说在异常类中
... //虚拟函数
}; //
void someFunction() // 抛出一个 validation
{ // 异常
...
if (a validation 测试失败) {
throw Validation_error();
}
...
}
void doSomething()
{
try {
someFunction(); // 抛出 validation
} //异常
catch (exception ex) { //捕捉所有尺度异常类
// 或它的派生类
cerr << ex.what(); // 挪用 exception::what(),
... // 而不是Validation_error::what()
}
}
#p#分页标题#e#
挪用的是基类的what函数,纵然被抛出的异常工具是Validation_error和 Validation_error范例,它们已经从头界说的虚拟函数。这种slicing行为毫不是你所期望的。
最后剩下要领就是通过引用捕捉异常(catch-by-reference)。通过引用捕捉异常能使你避开上述所有问题。不象通过指针捕捉异常,这种要领不会有工具删除的问题并且也能捕捉尺度异常范例。也不象通过值捕捉异常,这种要领没有slicing problem,并且异常工具只被拷贝一次。
我们回收通过引用捕捉异常的要领重写最后谁人例子,如下所示:
void someFunction() //这个函数没有改变
{
...
if (a validation 测试失败) {
throw Validation_error();
}
...
}
void doSomething()
{
try {
someFunction(); // 没有改变
}
catch (exception& ex) { // 这里,我们通过引用捕捉异常
// 以替代本来的通过值捕捉
cerr << ex.what(); // 此刻挪用的是
// Validation_error::what(),
... // 而不是 exception::what()
}
}
这里没有对throw举办任何改变,仅仅改变了catch子句,给它加了一个&标记。然而这个微小的改变能造成了庞大的变革,因为catch块中的虚拟函数可以或许如我们所愿那样事情了:挪用的Validation_erro函数是我们从头界说过的函数。
假如你通过引用捕捉异常(catch by reference),你就能避开上述所有问题,不会为是否删除异常工具而烦恼;可以或许避开slicing异常工具;可以或许捕捉尺度异常范例;淘汰异常工具需要被拷贝的数目。所以你还在等什么?通过引用捕捉异常吧(Catch exceptions by reference)!