异常
程序在出现异常时仍有良好的行为,是因为这个异常加入到了异常的考虑中。
如果需要一个绝对不会被忽略的异常信号发射方法,而且发射的信号后栈处理过程又能保证局部对象的析构函数被调用,则需要异常。
利用析构函数避免泄露资源
将资源释放封装在局部对象里,析构函数实现资源的释放,局部对象的析构函数即使在出现异常exception时也必定会被执行------以对象存放必须要自动释放的资源,并依赖析构函数释放。
智能指针就是最好的实例,用类似于指针的变量保存裸指针
在构造函数里阻止资源泄露
C++只会析构已经构造完成的对象。
构造函数内对于指针类型的成员变量赋值(new操作符)时可能会发出异常,如内存分配失败等,如何保证已经分配的内存得到释放呢,
try-catch语句可以,(依次序,后边分配出现异常时,将已经分配的释放掉)
指针类型的成员变量不再用裸指针,应用智能指针。
禁止异常流出析构函数之外
控制权基于exception的因素离开析构函数,而此时正有另一个exception处于作用状态,C++会调用terminate函数
例如:析构函数内抛出异常,而该析构函数的调用端,而这个析构函数本身是因为exception而被调用的,则terminate函数会被调用。
应全力阻止exception传出析构函数原因如下:
- 避免terminate函数在异常传播过程中的栈展开(stack-unwinding)机制中被调用
- 可以协助析构函数完成其应该完成的所有事情。
了解“抛出一个异常”与“传递一个参数”或“调用一个虚函数”之间的差异
“抛出一个异常”与“传递一个参数”
相同:传递方式均有三种:传值、传址、传引用
不同:
函数调用时控制权最终回到调用端;而抛出异常时控制权不会回到抛出端。
传递参数不一定发生复制,但是抛出异常一定会被复制,无论用什么方式抛出异常均会复制,且执行复制的为静态类型的构造函数,所以抛出异常比传递参数慢
抛出异常时可以用引用的形式,而不必非得是常引用。但是参数传递时临时对象不允许通过非常量的引用传递(非常量引用会被修改)
传值方式抛出异常会执行两次构造函数,一次为临时变量的构造,一次为临时变量到异常参数;传引用方式抛出异常会执行一次构造函数。
按指针抛出异常时,不能抛出一个指向局部变量的指针。
传递参数 传值可以进行隐式转换。但是抛出异常时默认不会进行隐式类型转换,catch double的语句不会捕获throw int。
抛出异常仅会有两种转换发生:
- 针对父类的捕获可以处理子类的异常;
- 可以从“有型指针”转为“无型指针”,即void*可以捕获任何指针类型的异常。
catch语句依出现次序做最佳匹配,分别有针对父类、子类的的catch语句,如果父类的在前边,则throw子类的异常时,捕获子类异常的不会被命中,所以要注意次序。catch语句总是做first fit。
静态类型和动态类型
class Base
{};
class Derived:public Base
{};
Base* ptr= new Derived();
//ptr的静态类型为Base
//ptr的动态类型为Derived
catch(Widget& w)
{
....
throw;//抛出的是原异常
}
catch(Widget& w)
{
....
throw w;//抛出的是复制后的异常,效率低 且可能不准确
}
以引用方式捕获异常
按指针捕获异常不需要复制对象,传递指针副本即可。但是可能会出现局部变量的地址,以及创建在堆上的指针(何时删除,如果不删除就会资源泄露)。所以最好采用按值或按引用的方式捕获异常。
按值捕获异常时会发生 基类捕获子类的异常时会发生“阉割” 按引用捕获异常则既可以减少构造次数,也不会发生“阉割”
明智引用exception specification
exception specification
void f2() throw(int);//throw(int)即为exception specificatio
如果函数抛出一个未列于其exception specification的异常,则函数的unexpected会被调用,而unexcepted的默认行为会调用terminate,程序会被终止。
不要为模板指定exception specification
A函数调用B函数,如果B函数未指定exception specification,那么A函数也不要设定exception specification
处理系统可能抛出的异常
C++允许以不同类型的exception取代非预期的exception,部署语句如下,部署完后所有的非预期的exception便会导致部署的异常被调用。
class UnexpectedException{};
void convertUnexpected()
{
throw UnexpectedException();
}
set_unexpected(convertUnexpected);//部署语句
如果非预期函数的替代者重新抛出当前exception,该exception会被标准类型bad_exception取而代之。
非预期的异常传递到函数后,函数unexpected会被调用。
了解处理异常的成本
处理异常会导致所需空间更大,所需的运行时间更长。
try语句会带来成本,代码大约整体膨胀5%~10%,运行速度变慢5%~10%。exception specification的成本和try的成本相同。
抛出异常的而导致的函数返回,其速度比正常情况下慢3个数量级。
请将try语句块和exception specification的使用限制于非用不可的地步,并在真正异常的情况下才抛出异常