13.17 章节小结
异常的常见例子有new无法取得所需内存、数组下标超界、运算溢出、除数为0和无效函数参数。
异常处理使程序可以捕获和处理错误,而不是任其发生和造成恶果。如果程序员不提供处理致命错误的措施,则程序终止。非致命错误通常允许程序继续执行,但会产生错误结果。
异常处理可以处理同步错误作为程序执行的结果。
异常处理并不处理异步情况,如磁盘I/O完成、网络消息到达、鼠标单击等等,这些情况最好用其他方法处理,如中断处理。
异常处理通常用于发现错误的部分与处理错误的部分在不同部分(不同范围)的情况。
异常不应为具体的控制流作为一种变换机制使用。控制流和常用的控制结构一般都比异常更清晰、更高效。
异常处理应当用于处理程序组件中与这些异常处理没有直接关系的异常。
异常处理应当用于处理函数、库、类等常用软件组件中的异常和组件本身不处理异常的情况。
异常处理应当用于在大型项目中以统一方式处理整个项目异常。
C++异常处理用于错误检测函数无法处理错误的情况,这种函数抛出异常。如果异常与catch块中的参数类型相符,则执行该catch块的代码。如果找不到相应的异常处理器,则调用termianate函数(默认调用函数abort)。
程序员在try块中放上出错时产生异常的代码。try块后面是一个或几个catch块。每个catch块指定捕获和处理一种异常。每个catch块包含一个异常处理器。
抛出异常时,程序控制离开时块,从catch块中搜索相应的异常处理器。如果try块中没有抛出异常,则跳过该块的异常处理器,程序在最后一个catch块之后恢复执行。
函数的try块中抛出异常,或者从try块直接或间接调用的函数抛出异常。
抛出异常之后,控制无法返回抛出点。
发生异常时,可以从异常点向异常处理器传递信息。这些信息是抛出对象的类型或抛出对象中的信息。
常见异常类型是char*,该类型只是包括一个错误消息,作为throw的操作数。
异常指定可以由指定函数抛出一列异常,将空异常指定语句放在函数的参数表之后表示该函数不抛出任何异常。
抛出异常时,指定相应类型的最近一个异常处理器(对抛出该异常的try块)捕获这个异常。
抛出异常时,生成和初始化 throw 操作数的一个临时副本,然后这个临时对象初始化异常处理器中的参数。异常处理器执行完毕和退出时,删除临时对象。
不一定总是显式检查错误。try 块可能不包含错误检查和 throw 语句,但时块中所指的代码可能导致执行构造函数中的错误检查代码。
异常会终止异常所在的程序块。
异常处理器放在 catch 块中。每个 catch 块以关键字 catch 开始,接着是括号内的包含类型(表示该块处理的异常类型)和可选参数名。后面是用花括号括起来的描述异常处理器的代码。
捕获异常时,执行 catch 块中的代码。
catch 处理器定义自己的范围。
catch 处理器中的参数可以命名也可以无名。如果是命名参数.则可以在处理器中引用这个参数,如果是无名参数(只指定匹配抛出对象类型的类型或用省略号表示所有类型),则处理器忽略所有抛出异常。处理器可以将对象重新抛出到外层try块中。
也可以指定自定义行为,在set_terminate函数调用中指定函数名参数,指定执行另一个函数,代替函数terminnate。
catch(...)表示捕获所有异常。
也许某个抛出对象没有任何匹配的异常处理器。这时匹配搜索会继续到外面一层try块。
异常处理器按顺序搜索,寻找匹配项.并执行第一个匹配的处理器。处理器执行完毕时.控制恢复到最后一个catch块后面的第一条语句。
处理器的顺序会影响处理异常的方法。
派生类对象可以由派生类类型的异常处理器和基类类型的异常处理器捕获。
有时程序可能处理许多密切相关的异常类型。这时不是提供不同的异常类和catch处理器,而是可以用一个异常类和catch处理器处理一组异常。发生每个异常时,可以生成具有不同 private数据的异常对象。catch处理器通过检查private数据区分异常的类型。
即使有准确匹配也还在匹配时要求标准转换,因为这个处理器出现在导致准确匹配的处理器之前。
默认情况下,如果找不到一个异常的处理器,则程序终止。
异常处理器无法直接访问try块范围中的变量。处理器所需的信息通常在抛出对象中传递。
异常处理器可以用不同方式编写,可以检查错误和确定调用terminate;可以再抛出;通过抛出不同异常可以将一种异常变为另一种异常;可以进行必要的恢复,并恢复执行最后一个异常处理器之后的语句;可以检查错误原因,删除错误原因和重新调用原先导致异常的函数(这不会生成无穷递归);可以向运行环境返回一些状态值等等。
应当将捕获基类类型的异常处理器放在捕获派生类类型的异常处理器之,否则基类类型的异常处理器捕获基类对象和从该类派生的所有对象。
捕获异常时,try块中可能有已经分配而还没有释放的资源。catch处理器应释放这些资源。
捕获异常的处理器也可以决定不处理异常。这时,处理器只要再抛出该异常。这种不带参数的throw再抛出异常。如果开始没有抛出异常,则再抛出异常调用terminate。
即使处理器能处理异常,不管这个异常是否进行处理,处理器仍然可以再抛出异常以便在处理器之外继续处理。再抛出异常由外层try块检测,由所在try块之后列出的异常处理器处理。
不带异常指定的函数可以抛出任何异常。
函数unexpected调用set_unexpected函数指定的函数。如果没有用set_unexpected函数指定函数,则默认调用terminate。
函数terminate可以显式调用,也可以在无法捕获抛出的异常时、在异常处理期间打乱堆栈时、作为调用unexpected的默认操作时或在异常导致堆栈解退时析构函数抛出异常的情况下调用terminate。
函数set_terminate和set_unexpected的原型分别在头文件<terminate.h>和<unexpected.h>中。
函数set_terminate和set_unexpected分别返回terminate和unexpected调用的最后一个函数的指针。这样就使程序员可以保存函数指针,以便后面恢复。
函数set_terminate和set_unexpected取函数指针为参数。每个参数指向返回类型为void和无参数的函数。
如果用户自定义终止函数的最后一个操作不是退出程序,则执行用户自定义终止函数的其他语句之后自动调用abort函数终止程序。
try块之外抛出的异常会使程序终止。
如果try块后面找不到处理器,则继续堆栈解退,直到找到相应处理器。如果最终找不到处理器.则调用terminate(默认用abort)退出程序。
异常指定列出可抛出的异常。函数可以抛出指定异常或派生类型。如果抛出异常指定中没有指定的异常,则调用函数unexpected。
如果函数抛出特定类类型的异常,则函数也可以抛出用public继承从该类派生的所有类异常。
要捕获异常,异常处理器要访问所抛出对象的复制构造函数。
构造函数中抛出异常时,对所有已构造的基类对象和抛出异常之前构造的成员对象调用析构函数。
如果发生异常时部分构造了对象数组.则只调用已构造数组元素的析构函数。
要捕获析构函数中抛出的异常,可以将调用析构函数的函数放在try块中,并提供相应类型的catch处理器。
利用异常继承使异常处理器可以用相当简单的符号捕获相关错误。虽然可以捕获每个派生类的异常对象,但更简练的方法是捕获基类的异常对象。
ANSI/ISO C++ 草案标准指定,出现new故障时抛出 bad_alloc 异常(在头文件<new>中定义)。
许多编译器目前还不支持草案标准,仍然在ncw故障时返回0。
函数 set_new_handler(原型在头文件<new>或<new.h>中)取一个函数指针参数,所指函数不取参数并返回void。函数指针注册为new失败时要调用的函数。用set_new_handler注册new处理器之后,new不会在发生故障时抛出bad_alloc。
auto_ptr 类对象维护动态分配内存的指针。当auto_ptr对象超出范围时,对指针数据成员进行一个delete操作,auto_ptr类模板提供了*和->运算符,因此auto_ptr对象可以像普通指针变量一样使用。
C++ 草案标准提供了标准库异常层次。这个层次以基类exception开始(在头文件<exception>中定义),该基类提供服务what(),在每个派生类中重定义。发出相应错误消息。
如果发生意外异常时,通过在函数的抛出表中加上std::bad_exception,unexpected()抛出bad_exception而不是(默认)终止程序或调用set_unexpected指定的另一函数。