在C++中catch异常时的参数应该用引用,主要原因还是对象,引用,指针的构造析构原理。下面用代码实例解释一下原因。
先来看我们定义了两个异常,SubException继承BaseException,有一个虚函数打印信息。
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的例子。
void CatchBaseObject(bool throwbase) { cout<<"==================="<<endl<<"CatchBaseObject "<<(throwbase?"ThrowBase":"ThrowSub")<<endl; try { throwbase? throw BaseException(): throw SubException(); } catch (BaseException e) { e.PrintMessage(); } }
输出结果如下:
=================== 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信息。测试代码如下:
void CatchBaseObjectForThrowSubAndThrowItAgain() { cout<<"==================="<<endl<<"CatchBaseObjectForThrowSubAndThrowItAgain "<<endl; try { try { throw SubException(); } catch (BaseException e) { e.PrintMessage(); throw e; } } catch (SubException e) { e.PrintMessage(); } }
输出如下:
===================
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的地方用的栈上的对象的地址,像如下代码所示:
void CatchBasePointer(bool throwbase) { cout<<"==================="<<endl<<"CatchBasePointer "<<(throwbase?"ThrowBase":"ThrowSub")<<endl; try { throwbase? throw &BaseException(): throw &SubException(); } catch (BaseException* e) { e->PrintMessage(); } }
得到输出如下:
=================== CatchBasePointer ThrowBase BaseExeption ~BaseExeption I'm BaseException =================== CatchBasePointer ThrowSub BaseExeption SubException ~SubException ~BaseExeption I'm BaseException
我们可以看到如下问题:
- 生成的那个exception时临时对象,除了try块就被析构了。
- catch住的异常对象发生了slicing。
为了保证不被析构,我们得new在堆上或者用static,如下代码展示用static:
void CatchBasePointerThrowStaticSub() { cout<<"==================="<<endl<<"CatchBasePointerThrowStaticSub "<<endl; try { static SubException s; throw &s; } catch (BaseException* e) { e->PrintMessage(); } }
得到输入如下:
=================== CatchBasePointerThrowStaticSub BaseExeption SubException I'm SubException
这时这个对象还在,因为用的是指针,所以也还有多态,但是要不要delete就不好办了,如果是static的,不需要delete,但是如果是new出来的,需要delete。
3.Catch引用。
如果换成引用,输出如下:
=================== CatchBaseRef ThrowBase BaseExeption I'm BaseException ~BaseExeption =================== CatchBaseRef ThrowSub BaseExeption SubException I'm SubException ~SubException ~BaseExeption
所以永远要用引用的方式来捕捉异常。本文例子的完整代码在github上。