异常处理
C++的异常处理机制被称为是不可恢复的:一旦异常被处理,程序的执行就不能够在异常被抛出的地方继续。如果这些 catch 子句不包含返回语句,在catch子句完成它的工作之后,程序的执行将在 catch 子句列表的最后子句之后继续进行。
抛出异常:throw 表达式(表达式可以是任何类型的对象)
抛出一个类时,可以调用类的构造函数 classname()
抛出类类型的异常步骤:- throw表达式通过调用类类型的构造函数创建一个该类的临时对象
- 创建一个类类型的异常对象,并传递给异常处理代码。该异常对象是第1步throw表达式创建的临时对象的拷贝,它通过调用类的拷贝构造函数而创建
- 在开始查找异常处理代码之前,在第1步中由throw表达式创建的临时对象被销毁。但是,在找到异常处理代码之前,该异常必须一直持续存在,而为了找到异常处理代码,或许要经过函数调用链中更上层的许多函数。所以,必须把这个临时对象拷贝到一个被称为异常对象的存贮区中,它保证会持续到异常被处理完毕。在某些情况下,编译器可能能够直接创建异常对象,而不需要创建第1步的临时对象。但是,消除该临时对象并不是 C++标准的要求,而且并不总是能够做到的。因为异常对象是通过拷贝throw表达式的值而创建的,所以,抛出来的异常总是在throw表达式中指定确切的类型 。
try块
try块以关键字try开始,后面是花括号括起来的能抛出异常的语句序列。try 块可以包含任何C++语句——表达式以及声明。
一个函数try块把一组catch子句同一个函数体相关联,如果函数体中的语句抛出一个异常,则考虑用跟在函数体后面的处理代码来处理该异常。函数try块对类构造函数尤其有用,将try放可以放在初始化列表前,try 块的复合语句包围了构造函数体,可以校验传入的参数是否符合要求。catch 捕获异常
catch子句由三部分构成:关键字catch、在括号中的单个类型或单个对象声明(异常声明)以及复合语句中的一组语句。如果异常声明的类型与被抛出的异常类型匹配,则选择这段处理代码来处理异常。3.1 异常对象
- catch 子句的异常声明可以是一个类型声明或一个对象声明。当需要获取throw表达式存储的信息时声明为一个对象。
- 异常对象总是在抛出点被创建,即使 throw 表达式不是一个构造函数调用,或者它没有表现出要创建一个异常对象,情况也是如此,被抛出的数据不会被改变。
- catch 子句中的异常声明也可以被改变成引用声明.catch子句就可以直接引用由throw表达式创建的异常对象,而不是创建一个局部拷贝了.
3.2 栈展开
栈展开:当一个异常被抛出时,为了寻找能处理该异常的catch子句,需要从抛出异常的函数内开始,向上通过嵌套的函数调用链,直到找到该异常的catch子句为止。在函数调用链中查找catch子句的过程被称作栈展开。
找到一个catch子句,以处理被抛出的异常的过程如下:
- 如果throw表达式位于try块中则检查与try块相关联的catch子句,看是否有一个子句能够处理该异常
1.1 如果找到一个catch子句,则该异常被处理;
1.2 如果没有找到catch子句,则在主调函数中继续查找; - 如果一个函数调用在退出时带着一个被抛出的异常,并且这个调用位于一个try块中。则检查与该try块相关联的catch子句,看是否有一个子句能够处理该异常:
2.1 如果找到了一个catch子句,则该异常被处理;
2.2 如果没有找到catch子句 则查找过程在主调函数中继续 - 这个过程沿着嵌套函数调用链向上继续,直到找到该异常的catch子句。只要一遇到能够处理该异常的catch子句,就会进入该catch子句,程序的执行在该处理代码中继续。
- 如果没有找到处理代码,程序就调用C++标准库中定义的函数terminate(),terminate()的缺省行为是调用abort()指示从程序非正常退出
3.3 重新抛出
语法:在catch语句的复合语句中,调用:”throw;”语句,将需要上级函数处理的异常重新抛出。重新抛出的异常就是原来的异常对象。如果catch异常不是引用,那么catch的复合语句操作的throw异常对象的拷贝,throw的异常对象不会改变。如果重新抛出前需要修改异常对象,catch异常需要声明引用。
3.4 catch-all处理代码
声明形式:catch(…){}
catch 子句被检查的顺序与它们在 try 块之后出现的顺序相同.一旦找到了一个匹配,则后续的 catch 子句将不再检查。这意味着,如果 catch(…)与其他 catch 子句联合使用,它必须总是被放在异常处理代码表的最后,否则就会产生一个编译时刻错误
异常规范
异常规范(exception specification)提供了一种方案,它能够随着函数声明列出该函数可能抛出的异常。它保证该函数不会抛出任何其他类型的异常。
语法:1. 异常规范跟随在函数参数表之后,用关键字throw来指定,后面是用括号括起来的异常类型表。如果一个成员函数被声明为 const 或 volatile 成员函数,则异常规范跟在函数声明的 const 和 volatile 限定修饰符之后。- 异常声明是函数接口的一部分,它必须在头文件中的函数声明上指定
- 如果函数声明指定了一个异常规范,则同一函数的重复声明必须指定同一类型的异常规范。
如果函数抛出了一个没有被列在其异常规范中的异常,则系统调用 C++标准库中定义的函数 unexpected(),unexpected()的缺省行为是调用 terminate() (如果函数抛出了一个没有被列在其异常规范中的异常,系统未必就会调用 unexpected()。如果该函数自己处理该异常,并且该异常在”逃离”该函数之前被处理掉,那么一切都不会有问题)
空的异常规范保证函数不会抛出任何异常:extern void no_problem() throw();
- 如果一个函数声明没有指定异常规范,则该函数可以抛出任何类型的异常
- 在被抛出的异常类型与异常规范中指定的类型之间不允许类型转换。有一个例外:如果一个异常规范指定了一个类,则该函数可以抛出”从该类公有派生的类类型”的异常对象。从一个基类公有派生出的派生类反映了一种 is-a 的关系,即派生类也是一种基类不算范围该规定。
- 这两个指针的异常规范不必完全一样,但是,用作初始值或右值的指针异常规范必须与被初始化或赋值的指针异常规范一样或更严格.
- 基类中虚拟函数的异常规范,可以与派生类改写的成员函数的异常规范不同。但是,派生类虚拟函数的异常规范必须与基类虚拟函数的异常规范一样或者更严格 。这可以确保当派生类的虚拟函数被通过基类类型的指针调用时,该调用保证不会违背基类成员函数的异常规范。
异常和继承
- 定义为类层次结构的异常
在实际的C++程序中,表示异常的类类型通常被组织成一个组(group)或一个层次结构。 - 处理类类型的异常
当异常被组织成类层次结构时,类类型的异常可能会被该类类型的公有基类的catch子句捕获到 - 异常对象和虚拟函数
如果被抛出的异常对象是派生类类型的,并且它被针对基类的catch子句处理,则catch于句一般不能使用派生类类型的特性。为了调用派生类对象的虚拟函数,异常声明必须声明一个指针或引用。
声明为引用:确保能正确地调用与异常类型相关联的虚拟函数
- 定义为类层次结构的异常
还没有评论,来说两句吧...