零 导论:
(1)异常特点:
*不能被忽略,忽略就会自动调用terminate函数,终止程序。
*产生异常后的stack处理过程(栈展开)能够确保局部对象的destructors被调用。
一 利用destructors避免泄漏资源:
*利用智能指针(如:auto_ptr)把资源封装在对象内(RAII),通常便可以在exceptions出现时避免泄漏资源。
二 在constructors内阻止资源泄漏:
(1)C++只会析构已构造完成的对象,对象只有在constructor构造完成才算是完全构建妥当。
(2)成员初始化列表只接受表达式。
(3)阻止方法:
*通常只需将所有可能的exceptions捕捉起来,执行某种清理工作,然后再重新抛出异常,使它传播出去。
*较好做法:将资源交给对象来管理,即智能指针。
三 禁止异常流出destructors之外:
(1)调用destructors的两种情况:
*对象正常状态下被销毁,即当它离开了它的生存空间或是被明确地删除。
*对象被exception处理机制销毁,即exception传播过程中的stack-unwinding(栈展开机制)。
(2)如果控制权因exception的因素离开destructor,而此时正有另一个exception处于作用状态,C++会调用terminate函数终止程序。
(3)阻止exception传出destructors之外的理由:
*避免terminate函数在exception传播过程的栈展开机制中被调用。
*协助确保destructors完成其应该完成的事情。
(4)阻止异常流出destructors的方法:try/catch语句块。
四 了解"抛出一个exception"与"传递一个参数"或"调用一个虚函数"之间的差异:
(1)函数参数和exception的传递方式有3种:by-value,by-reference,by-pointer。
(2)无论被捕捉的exception是什么传递方式,一个对象被抛出作为exception时,总会发生复制(在throw obj时)。
(3)主要差异:
*exceptions的对象总是被复制,如果以by value方式捕捉,它被复制两次。传递给函数参数的对象根据传递方式不一定被复制。
*抛出exception的对象的类型转换动作少(只有两种):继承框架中的类转换(基类的catch子句可以处理derived);从有型指针到void*指针的转换。
*catch子句以其“源代码出现顺序”被编译器检验比对,其中第一个匹配成功变执行。
五 以by reference 方式捕捉exception:
(1)by pointer方式的问题:
*需确保exception对象在抛出指针后依然存在(static)。
*抛出指针指向heap object时,应该避免资源泄漏。
*c++标准的exceptions都是对象,而不是对象指针。
(2)by value方式的问题:
*当exception objects被抛出时复制两次。
*会引起切割问题(slicing)。
(3)by reference方式:最佳,避免了资源泄漏问题,和标准异常类兼容,被复制一次,且没有切割问题。
六 明智运用exception specifications(异常说明):
(1)异常说明功能:
*明确指出一个函数可以抛出什么样的exceptions。
*如果函数抛出一个未列于异常说明的异常,这个错误会在运行期检查出来,于是unexpected函数会被自动调用。unexpected函数默认调用terminate函数。
(2)编译器只对异常说明做局部性检验,没有检验:调用某个函数可能会违反调用端本身的异常说明。
(3)使用异常说明的技巧:
*避免将异常说明放在“需要类型自变量”的templates上,因为没有方法知道一个template参数会抛出说明异常。
*如果A函数调用函数B,而B没有异常说明,那么A本身也不要设定异常说明。
*处理“系统”抛出的异常。
(4)C++允许以不同类型的exception取代非预期的exception。
*set_unexpected(函数名字):取代默认的unexpected函数。
*原理:如果非预期函数的替代者重新抛出当前对象,该exception会被标准类型bad_exception取而代之。
*上述操作后,让每一个异常说明都含有bad_exception,就不必担心程序遇上非预期的exception时终止执行。
(5)异常说明是一把双刃剑,加入函数之前,请考虑它所带来的程序行为是否真是你想要的。
七 了解异常处理的成本。