第三章,异常

条款12 了解“抛出一个exception”与“传递一个参数”或“调用一个虚函数”之间的差异

描述

(1)异常捕获catch子句与函数声明语法看起来一样,有by reference 和by value方式传递参数,但是catch子句与函数调用不同,调用函数后控制权会回到调用端,但是当抛出异常后,控制权不会回到抛出端。

(2)catch子句捕获总是会将传递的对象做拷贝,因为抛出异常后,便离开了原生对象的生存空间。即使,对象为static修饰的静态对象(存在直到程序结束),在抛出异常时捕获仍然会拷贝产生一个副本; 以by reference捕捉,仍然不能修改原对象,只能修改其拷贝后的副本。

(3)当对象被复制作为一个exception时,调用的是copy constructot;这个copy constructor相应于该对象的“静态类型”(基类),而非“动态类型”

class Widget{...};
class SpecialWidget : public Widget{...};

void passAndThrowWidget()
{
SpecialWidget localSpecialWidget;
...
Widget& rw = localSpecialWidget;
throw rw; //尽管localSpecialWidget是SpecialWidget类型,但是复制动作永远以对象的静态类型为本,因此这里抛出的是Widget exception
}

小结: exception对象是其他对象的副本

(4)差别:

catch(Widget& w)
{
...
throw; //没有复制行为,抛出原对象w
}

catch(Widget& w)
{
...
throw w; //有复制行为,抛出w的副本
}

(5)捕捉方式

catch (Widget w) ... //by value
catch (Widget& w) ...//by reference
catch (const Widget& w)...//by reference-to-const(等同by reference)

by value捕捉会产生两个副本,一个是任何exception都会产生的临时对象,另一个将临时对象复制到w上;而 by refernce只有第一种复制行为

(6)千万不要抛出指向局部对象的指针,因为离开生存空间后得到的是指向被销毁对象的指针。

(7)”exception传播” 不同于函数调用”参数传递”的类型吻合规则

double sqrt(double); // from <cmatch> or <match.h>

int i;
double sqrtOfi = sqrt(i);//允许隐式转换,将int 转为double

void f(int value)
{
try{
if(someFunction()){
throw value; //抛出int类型value
}
...
}
catch(double d){ //捕捉double类型的exception
...
}
...
}

try语句块抛出的int exception绝不会被“捕捉double exception”的catch子句捕获!这期间不会有类型转换的行为发生,int exception只能被catch(int i)捕获。

(8)exception与catch子句相匹配的过程中,只有两种转换可以发生;

一是“继承架构中的类转换” ,一个针对base class exception编写的catch子句可以处理类型为deried class的exception,这和前面提到的“静态类型”识别是一个思路。一个可接收最根源类的catch子句可以捕捉此继承体系下的所有exception。

这种所谓的“继承架构中的exception转换”规则可适用于by value,by reference以及by pointer 这3种形式

catch (runtime_error) ...       //by value
catch (runtime_error&) ... //by reference
catch (const runtime_error&) ... //by reference
catch (runtime_error*) ... //by pointer
catch (const runtime_error*) ... //by pointer

第二个允许发生的转换是从“有型指针”转为“无型指针”,所以一个catch(const void*)可以捕捉任何指针类型的exception

(9)”exception传播” 不同于函数调用”参数传递”的依出现顺序做匹配尝试

try{
...
}
catch (baseClass& bc){//接收baseClass与所有继承baseClass的派生类型异常
...
}
catch (derivedClass& dc){ //此语句绝不会执行,因为其可被接收的抛出异常均被catch (baseClass& bc)拦截
...
}

虚函数采用的是”best fit”(最佳吻合),而exception采用的是”first fit”(最先吻合)所以上述代码会收到编译器的警告,或者有的编译器会报错!因为第二个catch子句不会接收到任何异常,没有任何意义。

如果是必须的需求设计,就应该将catch (derivedClass& dc)写在catch (baseClass& bc)之前!

总结:
“传递对象到函数中,或是以对象调用虚函数”和“将对象抛出成为一个exception传播”之间,有3个主要差异:

1、exception objects总是会被复制生成一个临时对象,by value接收,会生成两个对象;而传递给函数参数的对象不一定会被复制

2、被抛出成为exception的对象,其被允许转换类型的动作比传递到函数去的对象少

3、catch子句第一个匹配成功的就执行,而虚函数调用是执行“与对象类型最佳吻合”的函数。