7.2 异常处理(Exception Handing)

Exception Handing快速检阅

C++的exception handing由三个主要语汇组件构成:

  1. 一个throw子句。它在程序某处发出一个exception,被抛出去exception可能是内建类型,也可是自定义类型。
  2. 一个或多个catch。每个catch子句都是一个exception handler,它用来表示这个子句准备处理某种类型的exception,并且在封闭的大括号内提供实际的处理程序。
  3. 一个try语句。它被围绕以一系列的叙述句(statements),这些叙述句可能引发catch子句起作用。

当一个exception被抛出,控制权会从函数调用中被释放出来,并寻找一个吻合的catch子句。如果没有吻合者,那么默认的处理例程terminate()会被调用。当控制权被放弃后,堆栈中的每一个函数调用也被推离(popped up)。这个程序称为unwinding the stack。每一个函数被推离堆栈之前,函数的local class objects的destructor会被调用。

Exception handing中比较不那么直觉的就是它对于那些看似没什么事做的函数所带来的冲击:

Point* mumble()
{
	Point*pt1,pt2;
	pt1 = foo();
	if(!pt1)
		return 0;
	
	Point p;
	
	pt2 = foo();
	if(!pt2)
		return pt1;
	
	...
}

如果有一个exception第一次调用foo()时被抛出,那么这个mumble()函数会被推出程序堆栈。由于调用foo()的操作并不在一个try语句内,也就不需要和一个catch子句吻合。这里没有任何的local class object需要析构。当第二次调用foo()时候抛出异常,exception handing机制就必须在推出程序堆栈前,先调用p的destructor。

在exception handing之下,上述两次调用foo()函数被视为两块语意不同的区域。因为当exception被抛出来的时候,这两个区域有不同的执行语意。欲支持exception handing,需要额外的一些“簿记”操作与数据。编译器的做法有两种:一种是把两块区域以个别的“将被摧毁之local objects”链表联合起来;另一种做法就是让两块区域共享一个链表,该链表会在执行期扩大或缩小。

在程序员层面,exception handing也改变了函数在资源管理的语意。例如下吗的函数对一块共享内存的locking和unlocking:

void mumble(void* arena)
{
	Point* p = new Point;
	smLock(arena);
	
	//如果异常触发
	//...
	smUnLock(arena);
	delete p;
}

如果上述语句异常被触发,函数没有unlock内存和delete p。

最直接的做法就是加上异常机制,或者使用RAII技术(将资源封装成class)。

使用RAII技术,会使那些拥有member class subobject或base class subobject(并且它们也都有constructors)的classes的constructor 更加复杂。一个class如果被部分析构,其destructor必须施行于那些已经被构造的subobjects和member objectssh身上。

例如,一个class X有member object A‘,B,C;都各有一对constructor和destructor。如果A的constructor抛出exception,不论A,B,C都不需要调用destructor。如果B的constructor抛出异常,A的destructor必须被调用,但C不用。处理所有意外事故,是编译器的责任。

同样道理:

//class Point3d: public Point2d{...};
Point3d* cvs = new Point3d[512];

如上代码,会发生两件事:

  1. 从heap中分配512个Point3d object所用的内存。
  2. 如果成功,先是Point2d constructor,然后是Point3d constructor,会被施行于每一个元素身上。

如果第27个元素的Point3dconstructor抛出异常,对于第27个元素,只有Point2d destructor需要调用。对于前26个元素,Point3d destructor和Point2d destructor都需要被调用。然后内存被释放回去。

对Exception Handing支持

对于一个exception发生时,编译器系统必须完成以下的事情:

  1. 检验发生throw操作的函数。
  2. 决定throw操作是否发生在try区段中。
  3. 若是,编译器必须把exception type拿来和每一个catch子句进行比较。
  4. 如果比较吻合,流程控制交到catch子句手中。
  5. 如果throw发送并不在try区段中,或没有一个子句吻合,那么系统必须摧毁所有的active local objects;并从堆栈中将目前的函数“unwind”掉;再进入到程序堆栈的下一个函数中区,然后重复2~5。

决定throw是否发生在一个try区段中

一个函数可以被想象成好几个区域:

  • try区段以外的区域,而且没有active local objects。
  • try区段以外的区域,但有一个及以上的active local objects需要析构。
  • rty以内的区域。

编译器必须标识出以上各区域,并使它们对执行期的exception handling系统有所作用。

将exception的类型和每一个catch子句的类型做比较

对于每一个被抛出来的exception,编译器必须产生一个类型描述器,对exception的类型进行编码。如果那是一个derived type,编码内容必须包括其所有base class的类型信息。只编进public base class的类型是不够的,因为这个exception可能被一个member function不再,而在一个member function的范围之中,derived class和nonpublic base class之间转换。

类型描述器(type descriptor)是必要的,因为真正的exception是在执行期被处理的,其object必须有自己的类型。

编译器还必须为每一个catch子句产生一个类型描述器,执行期的exception handler会将“被抛出的object类型描述器”和“每一个catch子句的描述器”进行比较,直到找到吻合的一个,或者直到堆栈被“unwound”而terminate()调用。

每一个函数会产生一个exception表格,它描述与函数相关的各区域、任何必要的善后处理代码以及catch子句的位置。

当一个实际对象在程序执行时被抛出,会发生什么?

当一个exception被抛出时,exception object会被产生出来并通常放置在相同形式的exception数据堆栈中。从throw端传递给catch子句的,是exception object的地址、类型描述器(或是一个函数指针,该函数会传回于该exception type有关的类型描述器对象)以及可能会有的exception object描述器(如果有人定义它的话)。

考虑如下的代码:

catch(exPoint p)
{
	//do something
	throw;
}

以及一个exception object,类型是exVertex,派生自exPoint。这两种类型都吻合,于是catch子句会起作用,那么p会发生什么事:

  • p将以exception object作为初值,就像一个函数参数一样。如果有定义(或编译器合成)一个copy constructor和一个destructor的话,它将会施行于local copy身上。
  • 由于p是一个object而不是一个reference,其内容被拷贝的时候,exception object的non-exPoint部分切割调用。此外,如果为了exception的继承而提供virtual function,那么p的vptr会被设置成exPoint的vtbl;exception object的vptr不会被拷贝。

当这个exception 再次抛出,p是拷贝的一个临时对象,并意味着丧失了原来的exception的exVertex部分。

如果exception object是引用,这是真正的类型。任何对此object的改变都是会被繁殖到下一个catch子句。

对于全局性的exception object,任何在throw中抛出的是一个被复制品,意味着一个catch子句对于exception object的任何改变都是局部性的,不会影响全局性的object。只有在一个catch子句评估完毕并且知道它不会再抛出exception之后,真正的exception object才会被摧毁。

于其他语言比起来,C++编译器支持EH机制所付出的代价最大。某种程度上是由于其执行期的天性以及对底层硬件的依赖,以及UNIX和PC两种平台对于执行速度和程序大小有着不同的取舍优先状态之故。

猜你喜欢

转载自blog.csdn.net/weixin_28712713/article/details/84981735
7.2
今日推荐