分析
根据传递的是参数或exception,发生的事情完全不同:
原因:
当你调用一个函数,控制权最终会回到调用端(除非函数失败以至于无法返回),但当你抛出一个exception,控制权不会再回到抛出端。函数参数和exception的传递方式有三种:
- by value
- by reference
- by pointer
eg.
//此函数从一个流中读取一个Widget
istream operator >>(istream& is,Widget& w);
void passAndThrowWidget()
{
Widget localWidget;
cin >> localWidget;//传递localWidget到函数operator>>里
throw localWidget;
}
-
当传递localWidget到函数operator>>里,没有进行复制,而是operator>>内的reference w被绑定于localWidget身上;
此时,对 w 做的任何事情,都是施加于localWidget身上的; -
这与抛出localWidget 异常不同:
捕获异常都将进行localWidget的复制操作,也就是说传递到catch子句中的localWidget是副本;
(C++规定要求被作为异常抛出的对象必须被复制) -
即使通过by reference的方式来捕获异常,也不能在catch中修改localWidget,仅能修改localWidget的副本。这也解释了参数传递和抛出异常的另一个差异:抛出异常运行速度比参数传递慢。
-
异常对象的复制操作由对象的复制构造函数完成,该构造函数是该对象的“静态类型”:
class Widget{};
class SpecialWidget:public Widget{}
void passAndThrowWidget()
{
SpecialWidget localSpecialWidget;
...
Widget& rw = localSpecialWidget;//rw代表一个SpecialWidget
thread rw; //抛出的异常的类型是Widget
//即rw的静态类型
}
catch(Widget& w)
{
...
throw; //捕获异常,重新抛出
//无论它是什么类型,如果w一开始是SpecialWidget,
//传递的便是SpecialWidget
}
catch(Widget& w)
{
...
throw w; //捕获异常,传递的是副本,类型总是Widget
}
必须使用throw;
才能重新抛出当前异常。
- 异常生成的拷贝是一个临时对象;
catch(Widget w); //by value
catch(Widget& w); //by reference
catch(const Widget w); //reference-to-const
//第一个语句:会建立两个被抛出对象的副本
//一个是所有异常的都必须建立的临时对象;
//第二个是将临时对象复制到w
//第二、第三个语句:只会建立临时对象。
C++允许把int到double隐式转换,所以i被悄悄变为double,并且其返回值也是double,一般来说catch子句匹配异常类型时不会进行这样的转换:
double sqrt(double);
int i ;
...
double sqrOfi = sqrt(i);
void f(int value)
{
try{
if(someFunction())
throw value; //抛出的是int
}
catch(double d) //只能处理double的异常
{
...
}
...
}
- catch子句进行异常匹配可以进行两种转换:
- 第一种就是继承类和基类之类的转换,一个用来捕获基类的catch子句也可以用来处理派生类类型的异常;
- 第二种允许从一个类型化指针转换成无类型指针,带有const void*的catch子句可以捕获任何类型的指针类型异常:
catch(const void*);
-
catch子句匹配顺序总是取决于它们在程序中出现的顺序。因此一个派生类异常可能被处理其基类异常的catch子句捕获,即使同时存在有可能直接处理该派生类异常的catch子句。
虚函数采用best fit策略,而异常处理采用first fit策略:
try{
...
}
catch(logic_error& ex) //捕获所有的logic_error异常
{
...
}
catch(invalid_argument& ex) //该处不会被执行
{
}
try{
...
}
catch(invalid_argument& ex) //处理invalid_argument异常
{
...
}
catch(logic_error& ex) //处理其他所有的logic_error异常
{
}
总结
把“一个对象传递给函数或一个对象调用虚函数”与“一个对象作为异常抛出”的区别主要有三点:
- 异常对象在传递时总进行复制。
当通过传值方式捕获异常时,异常对象被复制了两次;
对象作为参数传递给函数泽不一定需要被复制。 - 对象作为异常被抛出与被传递给函数相比,前者类型转换比后者少(只有两种转换)。
- catch子句在进行异常类型匹配的顺序是它们在源码中出现的顺序,第一个类型匹配成功的catch被执行;当以某对象调用一个虚函数,被选择执行的函数是与对象类型匹配最佳的类里,不论它是不是第一个。