<分析>:通过异常我们可以将异常的检测与问题的解决分离,这样程序的问题检测部分可以不必了解如何处理这些问 题,C++提供了异常处理机制,将异常处理机制知识点总结如下:
一、异常处理的基本概念:
(1)throw引发异常条件,try语句块以try关键字开始,并以一个或者多个catch关键字结束,catch必须紧跟在try后面,其中在try语句块中声明的变量是局部变量,不能在try语句块外面使用。被选中的catch处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那个;
(2)处理异常是逐渐向函数外层寻找catch语句的,如果不存在处理异常的catch子句,程序就跳到terminate函数,terminate函数又会调用abort函数,进行非正常退出。
(3)异常以类似于将实参传递给函数的方式抛出和捕获,异常是可以传递给非引用形参的任何类型对象,所以抛出的对象必须支持复制操作,不存在数组和函数类型的异常,相反,数组和函数类型的异常会被转换为指向数组第一个元素的指针和函数指针;
(4)抛出异常的时候,被抛出的对象局部存储已经不存在了,而是用throw表达式初始化一个异常对象,该异常对象由编译器管理,当catch语句处理完异常后撤销异常对象;
二、异常处理与指针:
(1)在异常处理中,一般不要抛出指针,因为抛出指针时,当异常处理离开指针所指对象的作用域后,指针会失效,这样会产生意想不到的错误;
(2)如果抛出指针的解引用,那么如果该指针是一个指向派生类对象的基类类型指针,那么该对象就会被分割,只抛出基类部分,所以不管如何,尽量避免抛出指针相关类型;
三、异常处理在析构函数和构造函数中的应用:
(1)抛出异常时,局部对象会自动调用析构函数进行顺序释放,如果是动态分配的内存(new),在释放(delete)之前发生异常,则不会进行释放,造成内存泄露,所以对于动态分配的内存容易发生异常者应该使用资源分配初始化(RAII)来进行管理资源,可以参考标准库的auto_ptr类;
(2)在析构函数中,一般不应该抛出异常,如果抛出则直接调用terminate函数非正常退出,这样会导致一些对象无法正常析构,造成内存泄露;
(3)构造函数可以抛出异常,可以保证正常撤销已经建立的对象。构造函数要处理初始化式中的异常,唯一的方法是将构造函数编写成函数测试块,如下:
A::A(int val,int n)
try:value(val),size(n)
{
//函数体内代码;
}catch(…)
{异常处理代码;}
这样编写成函数测试块,既可以处理初始化式产生的异常,也可以处理构造函数体内的异常;
四、catch子句的详解:
(1)catch子句必须包含的是已定义的完全类型,形参名字可选,如果需要访问该异常对象,则加上形参名字。catch形参类型容许的转换:
① 容许从非const到const转换
② 容许从派生类到基类转换
③ 容许数组到指针转换,函数到函数指针转换
(2)因为catch子句按出现次序匹配,所以最特殊的catch必须最先出现,必须将派生类型的处理代码出现在其基类类型catch子句前面,基类中虚函数的异常说明可以与派生类中对应虚函数的异常说明不同,但是派生类中的异常说明必须至少与基类中的异常说明同样严格。
(3)通常catch子句处理因继承相关的类型异常时,它就应该将自己的形参定义为引用,这样可以实现动态异常处理;catch可以改变它的形参,但是只有形参是引用时,才能向外传播那些改变;
(4)catch(...)可以捕获所有异常代码,一般将其放在最后一个,在其他所有异常不匹配的情况下进行匹配;
(5)不带有类型或表达式的throw可以重新抛出一个异常,它只能出现在catch作用块中;
五、标准异常类:
(1)exception头文件中定义的最常见的异常类。
(2)stdexcept头文件中定义几种常见的异常类。
(3)new头文件中定义了bad_alloc异常类型,因new无法分配内存抛出的。
(4)typeinfo头文件中定义的bad_cast异常类型。
(5)exception、bad_alloc、bad_cast只定义了默认的构造函数,无法创建这些类的对象,并且无法给对象赋值。其他的异常类则定义了一个用string初始化的构造函数。
(6)所有标准异常类型只定义了一个what的操作函数,返回const char* 描述异常类的指针,该函数是虚函数,所以适合类继承中动态类型的版本;
(7)标准异常类继承图如下所示: