在C++中catch异常时的参数应该用引用,主要原因还是对象,引用,指针的构造析构原理。下面用代码实例解释一下原因。
先来看我们定义了两个异常,SubException继承BaseException,有一个虚函数打印信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class BaseException { public: BaseException(){ cout<<"BaseExeption"<<endl; }; BaseException(BaseException& ){cout<<"BaseExeption Copy from BaseException"<<endl;}; BaseException(SubException& ){cout<<"BaseExeption Copy from SubException"<<endl;}; ~BaseException(){cout<<"~BaseExeption"<<endl;}; virtual void PrintMessage(){cout<<"I'm BaseException"<<endl;} };
class SubException:public BaseException { public: SubException(){cout<<"SubException"<<endl;} SubException(SubException& ):BaseException(){cout<<"SubException Copy from SubException"<<endl;}; ~SubException(){cout<<"~SubException"<<endl;} virtual void PrintMessage(){cout<<"I'm SubException"<<endl;} };
|
1. Catch对象。
再来看用对象做为参数catch的例子。
1 2 3 4 5 6 7 8 9 10 11 12
| void CatchBaseObject(bool throwbase) { cout<<"==================="<<endl<<"CatchBaseObject "<<(throwbase?"ThrowBase":"ThrowSub")<<endl; try { throwbase? throw BaseException(): throw SubException(); } catch (BaseException e) { e.PrintMessage(); } }
|
输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| =================== CatchBaseObject ThrowBase BaseExeption BaseExeption Copy from BaseException I'm BaseException ~BaseExeption ~BaseExeption =================== CatchBaseObject ThrowSub BaseExeption SubException BaseExeption Copy from BaseException I'm BaseException ~BaseExeption ~SubException ~BaseExeption
|
从这个输出中我们可以看出问题:
- 有2个对象被构建出来。
- catch BaseException时发生了slicing,sub的信息丢掉了。
除了这些问题之外,如果catch住在throw的话,要注意只能用throw,而不能用throw e,那样会再次生成临时对象,并且丢失原来的sub信息。测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void CatchBaseObjectForThrowSubAndThrowItAgain() { cout<<"==================="<<endl<<"CatchBaseObjectForThrowSubAndThrowItAgain "<<endl; try { try { throw SubException(); } catch (BaseException e) { e.PrintMessage(); throw e; } } catch (SubException e) { e.PrintMessage(); } }
|
输出如下:
1 2 3 4 5 6 7 8
| =================== CatchBaseObjectForThrowSubAndThrowItAgain BaseExeption SubException BaseExeption Copy from BaseException I'm BaseException BaseExeption Copy from BaseException Press any key to continue . . .
|
再次throw时已经变成了一BaseException,所以第二个catch不能抓住,导致程序终止。
2. Catch指针。
如果在throw exception的地方用的栈上的对象的地址,像如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12
| void CatchBasePointer(bool throwbase) { cout<<"==================="<<endl<<"CatchBasePointer "<<(throwbase?"ThrowBase":"ThrowSub")<<endl; try { throwbase? throw &BaseException(): throw &SubException(); } catch (BaseException* e) { e->PrintMessage(); } }
|
得到输出如下:
1 2 3 4 5 6 7 8 9 10 11 12
| =================== CatchBasePointer ThrowBase BaseExeption ~BaseExeption I'm BaseException =================== CatchBasePointer ThrowSub BaseExeption SubException ~SubException ~BaseExeption I'm BaseException
|
我们可以看到如下问题:
- 生成的那个exception时临时对象,除了try块就被析构了。
- catch住的异常对象发生了slicing。
为了保证不被析构,我们得new在堆上或者用static,如下代码展示用static:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void CatchBasePointerThrowStaticSub() { cout<<"==================="<<endl<<"CatchBasePointerThrowStaticSub "<<endl; try { static SubException s; throw &s; } catch (BaseException* e) { e->PrintMessage(); } }
|
得到输入如下:
1 2 3 4 5
| =================== CatchBasePointerThrowStaticSub BaseExeption SubException I'm SubException
|
这时这个对象还在,因为用的是指针,所以也还有多态,但是要不要delete就不好办了,如果是static的,不需要delete,但是如果是new出来的,需要delete。
3.Catch引用。
如果换成引用,输出如下:
1 2 3 4 5 6 7 8 9 10 11 12
| =================== CatchBaseRef ThrowBase BaseExeption I'm BaseException ~BaseExeption =================== CatchBaseRef ThrowSub BaseExeption SubException I'm SubException ~SubException ~BaseExeption
|
我们可以看到,没有多余的临时对象被创建出来,而且保持了多态,是我们期望的行为。
所以永远要用引用的方式来捕捉异常。本文例子的完整代码在github上。