<异常>
条款9:利用destructors避免泄露资源
在函数中,可以将资源封装在局部对象中,通常便可以在exceptions出现时避免泄露资源。是因为局部对象总是会在函数结束时被析构,不论函数如何结束,但唯一例外的就是调用longjmp而结束。
条款10:在constructors内阻止资源泄露(resource leak)
C++只会析构已构造完成的对象。对象只有在其constructor执行完毕才算是完全构造妥当。
面对尚未完全构造好的对象,为什么C++拒绝调用其析构函数?是因为若析构函数被调用于一个尚未完全的构造好的对象身上,那么这个析构函数如何知道构造函数现在构造了那些资源?若添加变量来统计资料的构造程度,将造成效率的降低。
条款11:禁止异常(exceptions)流出destructors之外
在两种情况下,析构函数会被调用:
- 当对象正常状态下被销毁,也就是当它离开了它的生存空间(scope)或是被明白地给予删除;
- 当对象呗exception处理机制销毁,即exception传播过程中的stack-unwinding(栈辗转开锁)机制。
有两个理由支持我们全力阻止exceptions传出destructors之外:
- 它可以避免terminate函数在exception传播过程的栈辗转开解机制中被调用;
- 它可以协助确保destructors完成其应该完成的所有事情。
条款12:了解抛出一个exception与传递一个参数或调用一个虚函数之间的差异
函数参数和exceptions的传递方式有三种:by value、by reference、by pointer。但是需要注意两者的不同,是因为如果你调用一个函数,控制权最终会回到调用端(除非失败以至于无法返回),但是当你抛出一个exception,控制权不会再回到抛出端。
exceptions与catch字句匹配的过程中,仅有两种转换可以发生:
- 继承架构中的类转换;
- 从一个有型指针转换为无型指针。所以一个针对const void *指针而设计的catch子句,可以捕捉任何指针型别的exception。
传递对象到函数或是以对象调用虚函数和将对象抛出成为一个exception之间的差异:
- exception objects总是会被拷贝:如果以by value方式捕捉,它们甚至被拷贝两次;
- 被抛出成为exceptions的对象,其被允许的型别转换动作要比被传递到函数去的对象少;
- catch子句以其出现于源代码的次序被编译器检验比对,齐总第一个匹配成功者便获得执行。
条款13:以by reference方式捕捉exceptions
catch-by-pointer方式是唯一在搬移异常相关信息时不需要拷贝对象的一种做法。它有两种做法:
- 抛出局部对象的指针,即从栈上分配的内存空间。但是该对象离开作用域将被析构,因此需要让异常对象在离开作用域后仍然存在,即全局对象或静态对象。
- 抛出一个新的指向堆的对象。但是需要考虑什么时候释放内存,以防止内存泄露。
catch-by-value方式:当异常对象被抛出时,就的拷贝两次。同时它可能印发对象切割问题。因为derived class exception objects被捕捉并被视为base class exceptions者,将失去其派生成分(参考《深入探索C++对象模型》)。
catch-by-reference方式:它不像catch-by-pointer,不会发送对象删除问题,因此也就不难捕捉标准的异常;它也不想catch-by-value,没有切割问题,而且异常只会被拷贝一次。
条款14:明智运用exception specifications
如果函数抛出一个未列于其exception specification的exception,这个错误将会在运行期被检测出来,于是特殊函数unexpected会被自动调用。其中,unexpected函数的缺省行为是调用terminate函数,而terminate函数的缺省行为是调用abort函数,因此程序如果违反exception specification,缺省结果就是程序被中断。
避免踏上unexpected之路的方法:
- 避免将exception specification放在需要型别自变量的templates身上;
- 如果函数A内调用了函数B,而函数B屋exception specification,那么A函数本身也不要设定exception specification;
- 处理系统可能抛出的exceptions。
C++允许你以不同型别的exceptions取代非预期的exceptions。
条款15:了解异常处理(exception handling)的成本
为了能够在运行期处理exceptions,程序必须做大量的簿记工作。在每一个执行点,它们必须能够确认如果发送exception,哪些对象需要析构;它们必须在每一个try语句块的进入点和离开点做记号;针对每个try语句块它们必须记录对应的catch子句以及能够处理的exceptions型别。
PS. 这里只是一个大体,如果想要了解更多的知识或者更好的运用,需要看《More effective C++》一书中对异常的具体条款描述。