您的位置:首页 > 编程语言 > C语言/C++

More Effective C++ 阅读笔记(九)--要用引用(reference)捕获异常

2008-08-06 09:35 429 查看
More Effective C++ 阅读笔记(九)--要用引用(reference)捕获异常
黑月亮 发表于 2005-10-4 11:30:00

要用引用(reference)捕获异常

当你写一个catch子句时,必须确定让异常通过何种方式传递到catch子句里。你可以有三个选择:与你给函数传递参数一样,通过指针(by pointer),通过传值(by value)或通过引用(by reference)。

我们首先讨论通过指针方式捕获异常(catch by pointer)。从throw处传递一个异常到catch子句是一个缓慢的过程,在理论上这种方法的实现对于这个过程来说是效率最高的。因为在传递异常信息时,只有采用通过指针抛出异常的方法才能够做到不拷贝对象(参见条款M12)
他们可能会这样写代码:
void someFunction()
{
exception ex; // 局部异常对象;
// 当退出函数的生存空间时这个对象将被释放。
...
throw &ex; // 抛出一个指针,指向
... // 已被释放的对象
}
这简直糟糕透了,因为处理这个异常的catch子句接受到的指针,其指向的对象已经不再存在。
另一种抛出指针的方法是建立一个堆对象(new heap object):
void someFunction()
{
...
throw new exception; // 抛出一个指针,指向一个在堆中建立的对象(希望
... // 操作符new — 参见条款M8—自己不要再抛出一个异常!)
}
这避免了捕获一个指向已被释放对象的指针的问题,但是catch子句的作者又面临一个令人头疼的问题:他们是否应该删除他们接受的指针?如果是在堆中建立的异常对象,那他们必须删除它,否则会造成资源泄漏。如果不是在堆中建立的异常对象,他们绝对不能删除它,否则程序的行为将不可预测。该如何做呢?这是不可能知道的。一些被调用者可能会传递全局或静态对象的地址,另一些可能传递堆中建立的异常对象的地址。通过指针捕获异常,将遇到一个哈姆雷特式的难题:是删除还是不删除?这是一个难以回答的问题。所以你最好避开它。
而且,通过指针捕获异常也不符合C++语言本身的规范。四个标准的异常――bad_alloc(当operator new(参见条款M8)不能分配足够的内存时,被抛出),bad_cast(当dynamic_cast针对一个引用(reference)操作失败时,被抛出),bad_typeid(当dynamic_cast对空指针进行操作时,被抛出)和bad_exception(用于unexpected异常;参见条款M14)――都不是指向对象的指针,所以你必须通过值或引用来捕获它们。

通过值捕获异常(catch-by-value)可以解决上述的问题,例如异常对象删除的问题和使用标准异常类型的问题。但是当它们被抛出时系统将对异常对象拷贝两次(参见条款M12)。而且它会产生slicing problem,即派生类的异常对象被做为基类异常对象捕获时,那它的派生类行为就被切掉了(sliced off)。这样的sliced对象实际上是一个基类对象:它们没有派生类的数据成员,而且当本准备调用它们的虚拟函数时,系统解析后调用的却是基类对象的函数。(当一个对象通过传值方式传递给函数,也会发生一样的情况――参见Effective C++ 条款22)。例如下面这个程序采用了扩展自标准异常类的异常类层次体系:
class exception { // 如上,这是
public: // 一个标准异常类
virtual const char * what() throw();
// 返回异常的简短描述.(在函数声明的结尾处的"throw()"
... //有关它的信息参见条款14)
};

class runtime_error: //也来自标准C++异常类
public exception { ... };
class Validation_error: // 客户自己加入个类
public runtime_error {
public:
virtual const char * what() throw();
// 重新定义在异常类中虚拟函数
... //抛出一个 validation异常
};
void someFunction()
{
...
if (a validation 测试失败) {
throw Validation_error();
}
...
}
void doSomething()
{
try {
someFunction(); // 抛出 validation异常
}
catch (exception ex) { //捕获所有标准异常类或它的派生类
cerr << ex.what(); // 调用 exception::what(),
... // 而不是Validation_error::what()
}
}
调用的是基类的what函数,即使被抛出的异常对象是runtime_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()
...
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: