一 异常处理:
三个词:被抛出对象、异常对象和异常说明符(又称形参、catch对象)。
(一)抛出类类型的异常:
*异常是通过抛出(throw)对象而引发的。该对象的类型决定应该激活哪个处理代码,被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那个。
*传递数组和函数类型形参时,该实验自动转换为一个指针。被抛出的对象发生同样的转换,因此不存在数组和函数类型的异常。
*处理异常会释放局部存储,所以被抛出对象就不能再局部存储,而是用throw初始化一个称为异常对象的特殊对象,由编译器管理,而且保证驻留在可能被激活的任意catch访问的空间。这个对象由throw创建,并被初始化为被抛出表达式的副本。异常对象传给对应的catch,并且在完全处理了异常之后撤销。
1.异常对象与继承:
当抛出一个表达式的时候,被抛出对象的静态编译时类型决定异常对象的类型。
2.异常对象与指针:
*在抛出指针解引用时,解引用的结果是一个对象,该对象可能和指针类型相同,也可能不同(继承层次中)。无论对象的实际类型是什么,异常对象都与指针的静态类型相匹配。
*直接抛出指针本身:抛出指向局部对象的指针式错误的,抛出指针的时候,必须确定进入处理代码时指针所指的对象存在。
(二)栈展开:
栈展开:抛出一个异常,首先要搜索的是抛出异常的函数,如果没有找到匹配的catch,则终止这个函数的执行,并在调用这个函数的函数中寻找匹配的catch,如果仍然没有找到相应的处理代码,该函数同样终止,搜索调用它的函数。如此类推,直到找到适当类型的catch为止。
1.为局部对象调用析构函数:
因异常而退出函数时,编译器保证适当地撤销局部对象:每个函数退出的时候,它的局部存储都被释放,在释放内存之前,撤销在异常之前创建的所有对象。如果对象时类类型,就自动调用该对象的析构函数。通常,编译器不撤销内置类型的对象。
*如果一个块直接分配资源,而且在释放之前发生异常,则栈展开期间不会释放该资源。例如:通过new动态分配的内存。
2.析构函数应该从不抛出异常:
栈展开期间会经常执行析构函数,在执行析构函数的时候是已经引发了异常但还没有处理它。如果在这个时候析构函数又发生异常,怎么处理?
答案:在为某个异常进行栈展开时,析构函数如果又抛出自己未经处理的另一个异常,将会导致调用标准库terminate函数,一般而言,terminate函数将调用abort函数,强制从整个程序非正常退出。
3.异常与构造函数:
*在构造函数内部经常会抛出异常。如果在构造函数对象的时候,则该对象可能只是部分被构造,它的一些成员已初始化,另外一些在异常发生之前还没有初始化。即使对象被部分构造了,也要保证将会适当的撤销已构造的成员。
*在初始化数组和其它容器的元素的时候,也可能发生异常,同样,也要保证将会适当的撤销已构造的元素。
4.未捕获的异常终止程序:
不能不处理异常。异常是足够重要的、使程序不能继续正常执行的事件。如果找不到匹配的catch,程序就调用库函数terminate。
(三)捕获异常:
*catch子句中的异常说明符(exception specifier)的类型决定了处理代码能够捕获的异常种类。类型必须是完全类型,类型的前置声明不行。当catch为了处理异常只需要了解异常的类型的时候,异常说明符可以省略形参名;如果处理代码需要知道类型之外的信息时,则异常说明符就包含形参名,catch就要这个形参名访问异常对象。
1.查找匹配的处理代码(catch子句):
在查找匹配的catch期间,找到的catch不必是与异常最匹配的那个catch,相反,将会选择第一个可以处理该异常的catch。因此,catch子句序列中,最特殊的catch必须最先出现。
*除了以下几种转换,异常类型与catch说明符的类型必须完全匹配:
-允许从非const到const的转换。
-允许派生类到基类的转换。
-将数组转化为指向数组类型的指针,将函数转换为指向函数类型的适当指针。
2.异常说明符:
进入catch的时候,用异常对象初始化catch的形参。异常对象本身是被抛出对象的副本。是否再次将异常对象复制到catch位置取决于异常说明符的类型(引用或非引用)。
*如果异常说明符不是引用,就将异常对象复制到catch形参中,catch操作的是异常对象的副本。
*如果异常说明符是引用,catch形参只是异常对象的别名。
3.异常说明符与继承:
基类的异常说明符可以捕获派生类的异常对象。
最佳实践:通常,如果catch子句处理因继承而相关的类型的异常,它就应该将自己的形参定义为引用。
*如果catch形参是引用形参,catch对象就直接访问异常对象,catch对象的静态类型可以和catch对象所引用的异常对象的动态类型不同。
*如果catch形参是非引用形参,则catch对象是异常对象的副本,如果catch对象是基类而异常对象时派生类,那么将异常对象切割为它的基类子对象。
4.catch子句的次序必须反映类型层次:
因为catch子句按出现的次序匹配,所以使用来自继承层次的异常的程序必须将它们的catch子句排序,以便派生类的处理代码出现在基类类型的catch之前。