教学目标
本章介绍异常处理(exception handling)。C++的扩展性可能大大增加错误发生的次数和种类。
本章介绍的特性可以让程序员编写更清晰、更健全、更具容错性的程序。利用这类技术开发的新系统已经取得成功。我们还将介绍何时不宜使用异常处理。
本章介绍的异常处理样式和细节基于Andrew Koenig 和Bjarne Stroustrup的论文《Exception Han dling for C++(revised)》,发表于1990年4月在美国旧金山举行的USENIX C++会议上。
错误处理代码的性质与数量随软件系统的应用和是否发布软件产品而不同。商业化产品通常要比自用软件提供更多的错误处理代码。
处理错误的方法多种多样。通常,错误处理代码是在整个系统代码中分布的。代码中可能出错的地方都要进行错误处理。这种方法的好处是程序员阅读代码时能够直接看到错误处理情况,确定是否实现了正确的错误检查。
但这种方法的问题是代码中受到错误处理的“污染”,使应用程序本身的代码更加晦涩难懂,难以看出代码功能是否正确实现。这样就使代码的理解和维护更加困难。
异常的常见例子有new无法取得所需内存、数组下标超界、运算溢出、除数为0和无效函数参数。
C++的新异常处理特性使程序员可以删除程序执行“主线条”中的错误处理代码,从而提高程序的可读性和可维护性。
利用C++式的异常处理,可以捕获所有类型的异常、捕获特定类型的所有异常和捕获相关类型的所有异常。这样就可以减少程序未能捕获的错误,使程序更加健壮。异常处理使程序可以捕获和处理错误,而不是任其发生和造成恶果。如果程序员不提供处理致命错误的措施,则程序终止。
异常处理可以处理除数为0之类的同步错误(synchronous error,在程序执行除法指令时发生)。
在程序执行除法之前,异常处理首先检查除数,如果除数为0,则抛出(throw)异常。
异常处理并不处理异步情况.如磁盘I/O完成、网络消息到达、鼠标单击等等,这些情况最好用其他方法处理,如中断处理。
异常处理使得系统从导致异常的错误中恢复。恢复过程即执行异常处理器(exceplion handler)。
异常处理通常用于发现错误的部分与处理错误的部分在不同部分(不同范围)的情况。与用户
进行交互式对话的程序不能用异常处理输入错误。
异常处理特别适合程序无法恢复但需要提供有序整理的情况,然后程序可以正常地结束。
对发生的范围与处理的范围不同的错误使用异常。而对发生的范围与处理的范围相同的错误使用其他方法。
避免在异常处理中进行错误处理以外的工作,这样可以提高程序的清晰性。
传统程序控制不用异常处理方法还有另一原因。异常处理是用于错误处理的,不是经常的活动,通常在程序准备终止时使用。这样,C++编译器的编写人员不像对正常应用程序代码一样对其实现最优化性能。
尽管异常处理可以进行错误处理以外的工作,但这样会程序性能下降。
编译器实现异常处理时通常使异常不发生时的异常处理代码开销极小或为0,而发生异常时,则会发生执行开销。异常处理代码的存在无疑会使程序占用更多内存。
使用传统控制结构的控制流通常比使用异常更清晰更有效。
传统程序控制使用异常处理方法的另一危险是堆栈解退,异常发生之前分配的资源可能无法释放。这个问题可以通过认真编程而避免。
异常处理能提高程序的容错能力。由于编写错误处理代码变得更为轻松,因此程序员更愿意提供错误处理代码,还可以用各种不同方法捕获异常,如根据类型或指定捕获任何类型的异常。
如今编写的大多数程序都只支持单线程执行。WindowsNT、OS/2和各种UNIX操作系统越来越重视多线程。本章介绍的方法在多线程程序中同样适用,但我们没有特别介绍多线程程序。
我们将介绍如何处理未捕获异常,考虑未捕获异常如何处理,介绍相关异常如何用公共基础异常类派生的异常类表示。
C++的异常处理特性随ANSI C++标准化的进行而不断发展。标准化在几十、几百人参与的大型软件项目中特别重要,每个人参与系统的不同组件,这些组件要在整个系统中交互,实现正确的功能。
异常处理很适合分别开发组件的系统。异常处理使组件组合更容易。每个组件进行自己的异常检测,这与另一范围的异常处理是分开的。
异常处理可以看成另一种从函数返回控制或退出代码块的方法。通常发生异常时,产生异常的函数的调用者、函数调用者的调用者或更深层的调用者处理这个异常。
虽然程序员可以用异常作为程序控制的替代方法,但异常处理应当只用于异常情况,处理程序组件中与这些异常处理没有直接关系的异常,处理函数、库、类等常用软件组件中的异常和组件本身不处理异常的情况,在大型系统中以统一方式处理异常。
对程序本身很容易处理的简单局部错误使用传统情误处理方法而不用异常处理。
涉及库时,库函数调用者通常用特定错误处理方法处理库函数中产生的异常。库函数很难进行满足用户独特需求的错误处理。因此.异常适合处理库函数产生的错误。
退出程序,使程序无法运行完毕或产生错误结果。实际上,对于许多错误类型,这是个好办法,特别是对于能让程序运行完毕的非致命错误,因为让程序运行完毕很可能使程序员误以为程序工作很顺利。这种方法也不适合任何任务关键的应用程序。资源问题也很重要,如果程序取得资源,则应先正常返回资源之后再终止。
退出程序会使其他程序无法使用其资源,从而造成资源泄漏。
C++异常处理用于错误检测函数无法处理错误的情况。这种函数抛出异常(throw an exception),但不能保证有相关的异常处理器。如果有,则异常处理器捕获和处理这个异常。如果没有该类异常相关的异常处理器,则程序终止。
程序员在try块中放上出错时产生异常的代码。try块后面是一个或几个catch块。每个catch块指定捕获和处理一种异常,而且每个catch块包含一个异常处理器。如果异常与catch块中的参数类型相符,则执行该catch块的代码。如果找不到相应异常处理器,则调用,terminate函数(默认调用函数abort)。
抛出异常时,程序控制离开try块,从catch块中搜索相应异常处理器(稍后将介绍如何形成相应异常处理器。如果try块中没有抛出异常,则跳过该块的异常处理器,程序在最后一个catch块之后恢复执行。
我们可以对异常指定函数throw,也可以指定函数不抛出任何异常。
函数的try块中抛出异常,或者从try块直接或间接调用的函数抛出异常。执行throw的点称为抛出点(throw point)。抛出点也指抛出表达式本身。抛出异常之后,控制无法返回抛出点。
发生异常时,可以从异常点向异常处理器传递信息。这些信息是抛出对象的类型或抛出对象中的信息。
抛出的对象通常是个字符串(错误消息)或类对象。抛出对象向处理该异常的异常处理器传递信息。
异常处理的关键是程序或系统中处理异常的部分可以和检测与产生异常的部分分开。