exception无法被忽略,如果一个函数以“设置状态变量”或“返回错误码”的方式发出一个异常信号,无法保证此函数的调用者会检查这个变量或错误码,于是程序的执行会一直继续下去远离错误发生地点,但是如果函数以抛出异常的方式发出异常信号,而该exception被捕捉,程序的执行便会立刻终止。
C++ exception较C语言longjmp,setjump的优势:发射exception之后stack的处理过程能够确保局部对象的destruction被调用。
传统方法避免资源泄露:try……catch ……finally
利用destructors:以一个类似指针的对象取代指针,当类似指针的对象被自动销毁时,可令其destructor调用delete
一般使用智能指针,但是需要主要的是智能指针不适用于数组对象,因为智能指针的析构函数为delete,一般用vector取代数组
C++只会析构已经构造完成的对象,对象只有在其constructor执行完毕才算是完全构造妥当;
如果对象没有new成功,赋值操作也不会正常进行,因此try……catch在这种情况下也不适合进行异常处理;同理,由于赋值操作不能正常进行,智能指针也不能发挥作用;
由于C++不自动清理那些构造期间抛出exception的对象,所以必须设计constructors使他们在这种情况下也能进行自我清理:将所有可能的exceptions捕捉起来,执行某种清理工作,然后重新抛出exception,使它继续传播出去即可。
(成员初始化列表只接受表达式)
当类的成员对象类型为常量指针时,需要在成员初始化列表中对其进行初始化,此时有两种方式处理exception:
1 增加成员函数用于初始化成员对象常量指针,并进行异常处理;
2 将常量对象指针定义为常量智能指针,大大简化操作,当宿主对象被销毁时,成员对象也会被自动销毁,不再需要手动删除。
两种情况下destructors会被调用,第一种情况是对象在正常情况下被销毁;第二种情况是当前对象被exception处理机制——也就是exception传播过程中的stack-unwinding(栈展开)机制销毁。
如果控制权基于exception的因素离开destructor,而此时又有另外一个exception处于作用状态,C++会调用terminate函数,将程序终止,甚至不等局部对象被销毁。
阻止异常溢出destructors之外的原因:1 可以避免terminate函数在exception传播过程中的栈展开机制中被调用;2 它可以协助确保destructors完成其应该完成的所有事情。
函数参数与exceptions的相同点:
1)传递方式:by value , by pointer , by reference
不同点:
1)当调用一个函数时,控制权最终会回归到调用端(除非函数失败以至于无法返回),但是当抛出一个异常,控制权不会再回到抛出端;
2)由于抛出exception总会导致对象的复制行为,因此抛出exception总比传参慢;
3)函数调用过程将一个临时变量传递给一个non-const reference参数是不允许的,但对exception则合法;
4)“抛出exception”会比“函数参数”多构造一个抛出物的副本
5)“调用者或抛出者”与“被调用者或捕捉者”之间存在的类型吻合规则:调用关系允许参数进行隐式类型转换,但是抛出exceptions不允许;
exceptions与catch子句匹配的过程中,仅有两种转换可以发生:
(1)继承架构中的类转换:一个针对base class exceptions而编写的catch子句,可以处理类型为derived class的exceptions(此规则适用于by value, by reference, by pointer);
(2)允许从“有型指针”到“无型指针”的转换: catch(const void*) 可以捕捉任何指针类型的exceptions;
6) catch子句总是依出现顺序做匹配尝试。因此,当try语句块中分别有针对base class而设计和针对derived class而设计的catch子句,一个derived class exceptions仍有可能被针对base class而设计的catch子句处理掉;虚函数采用的是“best fit(最佳吻合)”原则。
C++中一个对象被抛出作为exception时总会发生复制,复制行为由对象的copy constructor完成
by pointer捕获exception虽然避免了对象的复制,是最有效率的一种做法,但是有可能会出现传递指针指向的对象已被释放或无法判断是否释放指针所指向对象这两种问题;同时,4个标准的exception(bad_alloc,bad_cast,bad_typeid,bad_exceptions)均为对象,不是对象指针,必须以by value或by reference的方式捕捉他们。
by value每当exception objects被抛出时,就得复制两次,而且会引起切割问题,derived class exception objects被捕捉并被视为base o class exception,就会失去其派生部分,如此被切割过得对象其实就是base class Object,他们缺少derived class data member,当虚函数在其上被调用时,会被解析为base class的虚函数
by reference 不会发生对象删除问题,因此也就不难捕捉到标准的exception;与 by value不同,因此也不会出现切割问题,而且exception objects只会被复制一次。
如果函数抛出了一个未列于exception specification的exception,这个错误会在运行时被检验出来,于是特殊函数unexpected会被自动调用,unexpected的默认行为是调用terminate,而terminate的默认行为是调用abort,所以程序如果违反exception specification,默认结果是程序被终止,同时局部变量不会获得销毁的机会。
编译器只会对exception specification做局部性检验——同时也是C++命定不准拒绝的——是调用某个函数而该函数可能违反调用端本身的exception specification(有些严谨的编译器会对此发出警告);
由于编译器可以允许调用“可能违反当前函数本身的exception specification”的函数,并且这种调用行为可能会导致程序被迫终止,所以需要研究将这种不一致性降到最低的方法:
1) 避免将exception specification放在“需要类型自变量”的template身上
2)如果A函数内调用了B函数,而B函数无exception specification,那么A函数本身也不要设定exception specification
3)处理系统可能出现的exception:其中最常见的是bad_alloc