【C++ Exceptions】了解“抛出一个exception”与“传递一个参数”或“调用一个虚函数”之间的差异

分析

根据传递的是参数或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子句进行异常匹配可以进行两种转换
  1. 第一种就是继承类和基类之类的转换,一个用来捕获基类的catch子句也可以用来处理派生类类型的异常;
  2. 第二种允许从一个类型化指针转换成无类型指针,带有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异常
{							
	
}

总结

把“一个对象传递给函数或一个对象调用虚函数”与“一个对象作为异常抛出”的区别主要有三点:

  1. 异常对象在传递时总进行复制
    当通过传值方式捕获异常时,异常对象被复制了两次;
    对象作为参数传递给函数泽不一定需要被复制。
  2. 对象作为异常被抛出与被传递给函数相比,前者类型转换比后者(只有两种转换)。
  3. catch子句在进行异常类型匹配的顺序是它们在源码中出现的顺序,第一个类型匹配成功的catch被执行;当以某对象调用一个虚函数,被选择执行的函数是与对象类型匹配最佳的类里,不论它是不是第一个。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值