捕获异常的方法有三种:通过指针、通过传值和引用。
首先通过指针捕获异常(catch by pointer),通过指针方法效率最高,因为在传递异常时,通过指针抛出异常的方法不会拷贝对象:
class exception{...}; //来自C++标准
void someFunction()
{
static exception ex;
...
throw &ex; //抛出一个指针
...
}
void doSomething()
{
try{
someFunction(); //抛出一个exception*
}
catch(exception* ex)//捕获exception*,没有拷贝对象
{
...
}
}
为了能让程序正常运行,程序员定义异常对象时必须确保当程序控制权离开抛出的指针的函数后,对象能够生存。所以就要使用全局或者静态对象,如果忘记这个约束,catch到的指针,其指向的对象已经不存在。
另外一种方法抛出的指针的方法是建立在堆对象。
void someFunction()
{
...
throw new exception;//抛出一个指针,指向一个堆中对象
... //操作符new自己不要抛出异常
}
上述代码,但是在catch子句的又面临一个问题:他们是否删除指针,如果在堆中建立的异常对象,那 必须删除它,否则资源会泄露。如果不是在堆中建立的异常对象,他们绝对不能删除它,否则程序行为不可预测。
而且通过指针捕获异常也不符合C++语言本身的规范。四个标准异常bad_malloc(当operator new不能分配足够的内存时,被抛出),bad_cast(dynamic_cast针对一个引用操作失败时,被抛出),bad_typeid(当dynamic_cast对空指针进行操作,被抛出)和bad_exception(用于unexpected异常) ——都不是指向对象的指针,所以你必须通过值或者引用来捕获它们。
通过值捕获异常(catch by value)可以解决上述问题,但是当它被抛出时系统对异常对象拷贝两次,而且会产生切割问题:即派生类的异常通过基类异常对象来捕捉的时候,那么它们派生类被切割掉了:
class exception //标准异常类
{
public:
virtual const char* what() throw();
};
class runtime_error://标准异常类
public exception{...}
class Validation_error //重新定义的异常类
public exception
{
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
}
调用的是基类的what函数,即使抛出异常的对象是runtime_error或Validation_error类型并且它们已经重新定义的了这个虚函数。
最后就是通过引用方式捕获异常(catch by reference),这种能正确处理上述问题:不会有对象删除的问题而且捕获的异常不会导致切割问题:
void doSomething()
{
try{
someFunction();
}
catch(exception& ex) //捕获的是引用
{
cerr << ex.what(); //调用的是Validation_error::what
}
}
通过引用捕获异常(catch by reference)不会删除对象;也能避开异常对象的切割问题。