C++对象模型——异常处理 (Exception Handling)(第七章)
2015-08-20 22:48
288 查看
7.2 异常处理 (Exception Handling)
欲支持exception handling,编译器的主要工作就是找出 catch 子句,以预处理被丢出来的exception.这多少需要追踪程序堆栈中每一个函数的当前作用区域.同时,编译器必须提供某种查询exception objects的方法,以知道其实际类型(这直接导致某种形式的执行期类型识别,也就是RTTI).最后,还需要某种机制用以管理被丢出的object,包括它的产生,储存,可能的解构,清理以及一般存取.也有可能有一个以上的objects同时起作用.一般而言,exceptionhandling机制需要与编译器所产生的数据结构以及执行期的一个exception library紧密合作.在程序大小和执行速度之间,编译器必须有所选择:
为了维持执行速度,编译器可以在编译时期建立起用于支持的数据结构.这会使程序膨胀,但编译器可以几乎忽略这些结构,直到有个exception被丢出来.
为了维护程序大小,编译器可以在执行期建立用于支持的数据结构.这会影响程序的执行速度,但意味着编译器只有在必要的时候才建立那些数据结构.
Exception Handling快速检阅
C++的exception handling由三个主要的语汇组件构成:1.一个 throw 子句.它在程序某处发出一个exception.被丢出去的exception可以是内建类型,也可以是使用者自定类型.
2.一个或多个 catch 子句.每一个 catch 子句都是一个exception handler.它用来表示说,这个子句准备处理某种类型的exception,并且在封闭的大括号区段中提供实际的处理程序.
3.一个 try 区段.它被围绕以一系列的叙述句,这些叙述句可能会引发 catch 子句起作用.
当一个exception被丢出去时,控制权会从函数调用中被释放出来,并寻找一个吻合的 catch 子句.如果都没有吻合者,那么默认的处理例程terminate()会被调用.当控制权被放弃后,堆栈中的每一个函数调用也就被推离.这个程序称为unwinding the stack.在每一个函数被推离堆栈之前,函数的local class objects的destructor会被调用.
在程序员层面,exception handling也改变了函数在资源管理器上的语意.例如,下面的函数中含有对一块共享内存的locking和unlocking操作.虽然看起来和exceptions没有什么关联,但在exception handling下并不保证能够正确运行:
void mumble(void *arena) { Point *p = new Point; smLock(arena); // function call // 如果有一个exception在此发生,问题就来了 // ... smUnlock(arena); // function call delete p; }本例中,exception handling机制把整个函数视为单一区域,不需要操心"将函数从程序堆栈中'unwinding'"的事情.然而从语意上说,在函数被推出堆栈之前,需要unlock共享内存,并 delete p,让函数成为"exception proof"的最明确的方法就是插入一个default catch 子句,像这样:
void mumble(void *arena) { Point *p; p = new Point; try { smLock(arena); // ... } catch (...) { smUnlock(arena); delete p; throw; } smUnlock(arena); delete p; }这个函数现在有了两个区域:
1.try block以外的区域,在那里,exception handling机制除了"pop"程序堆栈之外,没有其他事情要做.
2.try block以内的区域(以及它所联合的default catch 子句)
请注意,new 运算符的调用并非在 try 区段,这是错误吗?如果 new 运算符在Point constructor配置内存后发生一个exception,那么内存既不会被unlocking,p也不会被 delete.这是正确的语意吗?
是的,它是.如果 new 运算符丢出一个exception,那么就不需要配置heap中的内存,Point constructor也不需要被调用,所以也就没有理由调用 delete 运算符.然而如果是在Point constructor中发生exception,此时内存已配置完成,那么Point中任何构造好的合成物或子对象都将自动被解构,然后heap内存也会释放掉.无论哪种情况,都不需要调用 delete 运算符.
类似的道理,如果一个exception是在 new 运算符执行过程中被丢出,arena所指向的内存就绝不会被locked,因此,也没有必要unlock.
处理这些资源管理问题,一个好的办法是将资源需求封装于一个 class object体内,并由destructor来释放资源.
如果程序员写下:
// class Point3d : public Point2d { ... }; Point3d *cvs = new Point3d[512];会发生两件事:
1.从heap中配置足以给512个Point3d objects所用的内存
2.如果成功,先是Point2d constructor,然后是Point3d constructor,会施行于每一个元素上.
如果第27个元素的Point3d constructor丢出一个exception,会怎么样呢?对于第27个元素,只有Point2d destructor需要调用执行,对于前26个元素,Point3d destructor和Point2d destructor都需要起而执行,然后内存必须被释放回去.
对Exception Handling的支持
当一个exception发生时,编译系统必须完成以下事情:1.检验发生 throw 操作的函数.
2.决定 throw 操作是否发生在 try 区段.
3.若是,编译系统必须把exception type拿来和每一个 catch 子句比较.
4.如果比较吻合,流程控制应该交到 catch 子句中.
5.如果 throw 的发生并不在 try 区段中,或没有一个 catch 子句吻合,那么系统必须(a)摧毁所有active local objects,(b)从堆栈中将当前的函数"unwind"掉,(c)进行到程序堆栈中的下一个函数中,然后重复上述步骤2~5.
决定 throw 是否发生在一个 try 区段中
一个函数可以想象成好几个区域:try 区段以外的区域,而且没有active local objects.
try 区段以外的区域,但有一个(以上)的active local objects需要解构.
try 区段以内的区域.
编译器必须标示出以上各区域,并使它们对执行期的exception handling系统有所作用.一个很好的策略就是构造出program conter-range表格.
program counter内含下一个即将执行的程序指令,为了在一个内含 try 区段的函数中标示出某个区域,可以把program counter的起始值和结束值储存在一个表格中.
当 throw 操作发生时,当前的program counter值被拿来与对应的"范围表格"进行比较,以决定当前作用中的区域是否在一个 try 区段中.如果是,就需要找出相关的 catch 子句.如果这个exception无法被处理,当前的这个函数会从程序堆栈中被推出,而program counter会被设定为调用端地址,然后这样的循环再重新开始.
将exception的类型和每一个 catch 子句的类型做比较
对于每一个被丢出来的exception,编译器必须产生一个类型描述器,对exception的类型进行编码.如果那是一个derived type,则编码内容必须包括其所有base class 的类型信息.只编进 public base class 的类型是不够的,因为这个exception可能被一个member function捕捉,而在一个member function的范围中,在derived class 和nonpublic base class 之间可以转换.类型描述器是必要的,因为真正的exception是在执行期被处理,其object必须有自己的类型信息.
编译器还必须为每一个 catch 子句产生一个类型描述器.执行期的exception handler会对"被丢出的object的类型描述器"和"每一个cause子句的类型描述器"进行比较,直至找到吻合的一个,或是直到堆栈已经被"unwound"而terminate()已被调用.
每一个函数会产生一个exception表格,它描述与函数相关的各区域,任何必要的善后码以及 catch 子句的位置.
当一个实际对象在程序执行时被丢出,会发生什么事
当一个exception被丢出时,exception object会被产生出来并通常放置在相同形式的exception数据堆栈中.从 throw 端传染给 catch 子句的是exception object的地址,类型描述器(或是一个函数指针,该函数会传回与该exception type有关的类型描述器对象),以及可能会有的exception object描述器.相关文章推荐
- 传送门2号 - 算法 x C++
- 警惕C++隐式转换(More Effectiv C++_5(运算符))
- 迷宫游戏C语言实现
- C++中引用(&)的用法和应用实例(转)
- C/C++笔试面试系列之一
- C#与C++的命名空间
- 零基础学C语言 笔记一 变量 printf
- 从背后知道C语言程序是怎么运行的
- C++头文件与实现文件分别写什么
- C语言-函数
- C++ string学习(转)
- C++之程序时间统计类实现
- 二叉树的C++实现代码
- C++那些细节--函数的默认参数
- C语言中do...while(0)的妙用-避免goto
- 推箱子
- C
- C++,当函数参数或者返回值是对象......(★firecat推荐★)
- C++ Primer 5e chapter 7
- Python实例浅谈之三Python与C/C++相互调用