性的最有效方法之一C语言实现出错处理的方法

上传人:大米 文档编号:570145110 上传时间:2024-08-02 格式:PPT 页数:76 大小:126KB
返回 下载 相关 举报
性的最有效方法之一C语言实现出错处理的方法_第1页
第1页 / 共76页
性的最有效方法之一C语言实现出错处理的方法_第2页
第2页 / 共76页
性的最有效方法之一C语言实现出错处理的方法_第3页
第3页 / 共76页
性的最有效方法之一C语言实现出错处理的方法_第4页
第4页 / 共76页
性的最有效方法之一C语言实现出错处理的方法_第5页
第5页 / 共76页
点击查看更多>>
资源描述

《性的最有效方法之一C语言实现出错处理的方法》由会员分享,可在线阅读,更多相关《性的最有效方法之一C语言实现出错处理的方法(76页珍藏版)》请在金锄头文库上搜索。

1、第九章 异常处理性的最有效方法之一C语言实现出错处理的方法程序的错误有两大类: 编译链接错误:这类错误是由程序的语法错误(例 如关键字错误、变量未定义、语句结束缺分号、括 号失配、结构失配等)和其他错误(函数只声明未 定义、缺少库的链接配置等)引起的。这类程序错 误发生在程序的编译链接过程中,对于一个具有一 定经验的编程人员是容易解决的。 运行错误:这类程序错误发生在程序的运行期间, 主要表现在计算过程中的被0除、内存空间不足、数 据的输入输出错误等。这类程序错误只靠编程人员 的经验是难以避免的。性的最有效方法之一C语言实现出错处理的方法错误修复技术是解决程序运行错误,提高代码健壮性的最有效方

2、法之一。C 语言实现出错处理的方法是出错与错误处理的紧耦合,即检查被调函数的返回值或输出信息,以便确定是否发生错误,作出相应的处理。这种出错处理存在两个主要问题: 出错处理的繁琐和错误检查引起的代码膨胀将不可避 免地降低程序的执行效率,增加程序的阅读困难。 被调用函数只清楚出错原因而不清楚被调用环境,因 此缺乏处理错误的依据。因此这种将用户函数与出 错处理紧密结合的方法将造成使用出错处理的不方 便和难以接受。性的最有效方法之一C语言实现出错处理的方法正是因为上述原因,使得不少程序设计人员在实际设计中常常 “忽略” 出错处理,似乎是在 “不会出错” 的状态下编程,这会严重地降低程序代码的健壮性。

3、异常处理是 C+ 语言的一个重要特征,它提出了出错处理更加完美的方法。 出错处理代码的编写不再繁琐,也不须将出错处理代 码与功能代码紧密结合。在可能发生错误的函数中 加入出错代码,并在后面调用该函数的程序中加入 错误处理代码。如果程序中多次调用一个函数,可以在程序中加入一个专门用于被调函数的出错处理 函数。性的最有效方法之一C语言实现出错处理的方法 错误发生是不会被忽略的。如果被调用函数需发送 一条出错信息给调用函数,它可向调用环境发送一 个描述错误信息的对象。如果调用环境没有捕获该 错误信息对象,则该错误信息对象会被自动向上一 层的调用环境发送;如果调用环境无法处理该错误 信息对象,则调用环

4、境可以将该错误信息对象主动 发送到上一层的调用环境中;直到该错误信息对象 被捕捉和处理。性的最有效方法之一C语言实现出错处理的方法9.1 C 语言的出错处理 在通过对被调用函数的返回或对断言宏 assert() 的判断结果的检查能够确切定后续操作的情况下,出错处理就变得十分明确和容易了,因为可以通过程序执行的当前运行环境得到所有必要的信息。然而能够这样处理的错误都是环境一般都是简单的普通错误。 如果错误问题发生时,在程序当前运行环境中无法获得足够的错误发生和处理的信息,则需要从更大的运行环境中获取出错处理信息。C 语言处理这类错误情况的典型方法有三种:性的最有效方法之一C语言实现出错处理的方法

5、 出错信息可以通过函数的返回值获得。如果返回值 不足以描述出错信息,则可设置全局错误判断标志 (标准 C 语言中全局变量 errno 以及系统运行库函 数 perror(const char *string) 、strerror(int errnum) 支持这 一方法)。由于这种方法要对每个函数调用都进行错 误检查,这将十分繁琐并增加程序的混乱度。另 外,偶然出现异常的函数返回值可能并不反映什麽 问题。性的最有效方法之一C语言实现出错处理的方法 可使用 C 信号处理库中的 signal 函数设置中断信号 处理,和使用 raise 函数向正在运行的程序发送信 号。这两个函数的原型如下: void

6、(*signal(int sig, void(_cdecl *func)(int sig, int subcode) ) (int sig); int raise( int sig ); 信号处理库的使用者必须清楚地了解、恰当地定义 和设置中断信号处理。同时对于大型项目,不同库 之间的信号可能会产生冲突。因此,信号处理库的 使用有一定的难度。性的最有效方法之一C语言实现出错处理的方法 使用 C 标准库中的设置跳转函数 setjmp 和非局部跳 转函数 longjmp 实现出错和错误处理。这两个函数 的原型如下: setjmp(jmp_buf env); longjmp(jmp_buf env,

7、 int value); 调用 setjmp 函数在程序中存储一典型的正常状态, 如果进入错误状态,longjmp 可恢复由 setjmp 函数所 设定的状态,并且状态被恢复时的存储地点与错误 发生地点紧密联系。性的最有效方法之一C语言实现出错处理的方法在较早的 Visual C+ 版本(例如 VC+ 6.0)中 C 语言的信号处理技术和 setjmp/longjmp 函数被调用时不能正确地调用类对象的析构函数,所以一个描述出错信息的对象不能被正确地清除。如果出错对象不能被清除,则该对象将被保留下来且不能再次被正确地存取,因此,实际上是不可能有效、正确地从异常情况中恢复。所以在 C+ 编程中不

8、推荐使用 C 语言中这种处理出错的方法。例例9-19-1 描述了 setjmp/longjmp 函数的这一特点。性的最有效方法之一C语言实现出错处理的方法在 VC+ 6.0 中的运行结果:tornado, witch, munchkins.theres no place like hometheres no place like hometheres no place like homerainbow() is called.Auntie Em! I had the strangest dream.分析: 由于 main 函数中 setjmp 的调用(保存当前运行点环 境)后返回 0,导致 i

9、f 分支的执行,显示了前 5 行 执行信息。性的最有效方法之一C语言实现出错处理的方法 全局函数 OZ 被调用,其中 longjmp 的调用结果使得 程序的运行恢复到由 setjmp 保存的运行点,导致使 setjmp 再次被调用。由于保存运行点环境的缓冲区 kansas 被 setjmp 的第一次调用设置,使得 setjmp 的 第二次调用返回值为非 0,导致 else 分支执行,显 示第 6 行运行信息,但 rainbow 对象 RB 未被析构。结论: 程序中调用 setjmp 和 longjmp 的方法提供了修复程 序运行错误的典型结构和方法。 使用 setjmp 和 longjmp 可

10、能无法析构错误发生前创 建的对象,不能满足面向对象程序的错误处理。性的最有效方法之一C语言实现出错处理的方法9.2 C+ 语言的出错处理虽然 C 语言的出错处理技术和方法在 C+ 语言的程序设计中仍然可以使用,但更重要的是 C+ 提供了一套符合面向对象程序设计的出错处理技术 异常处理技术。异常处理的特点主要表现在: 异常的产生和异常的处理分离。 异常的信息数据和行为被封装成独立的类对象。 结构化的异常处理为异常对象的捕获、处理、传递 提供了有效、方便的编程手段。 能确保残留对象在异常处理时被彻底析构。性的最有效方法之一C语言实现出错处理的方法9.3 异常对象的创建和抛出如果程序发生了异常情况,

11、而在当前运行环境中无法获取处理异常的足够信息,就可以创建一个包含异常信息和行为的对象,并将该对象从发生异常的运行环境中抛出,发送到上一层运行环境中,这称为异常的创建和抛出,例如:throw myerror(something bad happened);分析: 关键字 throw 的作用是抛出异常对象,被抛出的对象可以是包括系统预定义类型在内的任何类型,当然多数情况为自定义异常类型,如本例中的 myerror 就是一个以字符串为参数创建的自定义异常对象。性的最有效方法之一C语言实现出错处理的方法 在函数中使用关键字 throw 抛出异常类对象都是函 数正常执行中不存在的。抛出异常类对象的结果是

12、 导致函数退出执行,但不是函数设计的正常返回。 因此,异常类型可以视为是函数异常退出作用域的 返回值类型。 函数的正常返回和对函数返回的检查和处理是处于 同一作用域的,而对异常的处理作用域与异常抛出 作用域可能相距很远。注意,只有完整创建的异常 对象才能在异常被处理时被清除,而函数返回(包 括异常返回)时,函数作用域内的所有对象均被清 除。性的最有效方法之一C语言实现出错处理的方法 函数可以根据错误发生的原因不同,抛出不同类型 的异常对象,使函数的调用者可以在更大范围的程 序上下文环境中考虑对异常的处理。性的最有效方法之一C语言实现出错处理的方法9.4 异常的捕获和处理函数在发生错误时能以抛出

13、异常对象的方式结束函数执行是建立在假定该异常对象能被捕获和处理的前提下的。这一假定在 C+ 中是成立的,这也是异常处理的一个优点。完成函数调用时的异常测试,异常对象的捕获和处理是由 try - catch 结构实现的,使得处理程序运行错误的编码变得方便、有效,并具有完全的结构化和良好的可读性。该结构的一般形式如下:try 被测试的程序代码 catch(异常类型 异常对象名) 异常处理的程序代码 性的最有效方法之一C语言实现出错处理的方法9.4.1 测试块 try测试块 try 的作用是使处于该块中的程序代码执行可能抛出的异常对象能在后续的异常处理器中被捕获,从而确定如何处理。因此,调用一个函数

14、,并期望在函数调用者所在程序运行环境中使用异常处理的方法解决函数可能发生的错误,就必须将函数调用语句置于测试块 try 中。否则函数所抛出的异常对象就不能被后续的异常处理器捕获,从而使异常对象被自动传递到上一层运行环境,直至被操作系统捕获和处理,导致程序被终止执行。性的最有效方法之一C语言实现出错处理的方法9.4.2 异常处理器异常发生后,被抛出的异常对象一旦被随后的异常处理器捕获到,就可以被处理。根据在当前运行环境中能否解决引起异常的程序运行错误,对异常对象的处理有两种: 尝试解决程序运行错误,析构异常对象。 无法解决程序运行错误,将异常对象抛向上一层运 行环境。为此,异常处理器应该具备捕获

15、一个以上任何类型的异常对象的能力,每个异常对象的捕获和处理由关键字 catch 引导。 例如:性的最有效方法之一C语言实现出错处理的方法try / code that may generate exceptioncatch(type1 id1)/ handle exceptions of type1catch(type2 id2)/ handle exceptions of type2/ etc性的最有效方法之一C语言实现出错处理的方法 每个 catch 语句相当于一个以特定的异常类型为单 一参数的小型函数; 标识符 id1、id2 等如同函数中的参数名,如果对引 起该异常对象抛出的程序运行的

16、错误处理中无须使 用异常对象,则该标识符可省略; 异常处理器部分必须紧跟在测试块 try 之后; catch 语句与 switch 语句不同,即每个 case(情况) 引起的执行需要加入 break 实现执行的结束; 测试块 try 中不同函数的调用可能会抛出相同的异常 对象,而异常处理器中对同一异常对象的处理方法 只需要一个。性的最有效方法之一C语言实现出错处理的方法异常处理的两种模式 终止与恢复: 终止模式 如果引起异常的是致命错误,即表明程序运行进入 了无法恢复正常运行的状态,这时必须调用终止模 式结束程序运行的异常状态,而不应返回异常抛出 之处。 恢复模式 恢复意味着期望对异常的处理能

17、够修复异常状态, 然后再次对抛出异常对象的函数进行测试调用,使 之能够成功运行。性的最有效方法之一C语言实现出错处理的方法 如果希望程序具有恢复运行的能力,就需要程序在 异常处理后仍能继续正常执行,这时异常处理就更 像一个被调用的函数。在程序需要进行恢复运行的 地方,可以将测试块 try 和异常处理器放在 while 循 环中,直到测试调用得到满意的结果。性的最有效方法之一C语言实现出错处理的方法9.4.3 异常规格说明编写异常处理器必须知道被测试调用的函数能抛出哪些类型的异常对象。C+ 提供了异常规格说明语法,即在函数原型声明中,位于参数表列之后,清晰地告诉函数的使用者:该函数可能抛出的异常

18、类型,以便使用者能够方便地捕获异常对象进行异常处理。带有异常规格说明的函数原型说明的一般形式:返回类型 函数名(参数表列) throw (异常类型名, )性的最有效方法之一C语言实现出错处理的方法使用异常规格说明的函数原型有三种: 抛出指定类型异常对象的函数原型: void function() throw(toobig, toosmall, divzero); 能抛出任何类型异常对象的函数原型: void function(); 注意,该形式与传统的函数原型声明形式相同。 不抛出任何异常对象的函数原型: void function() throw();为了实现对函数的安全调用和对函数执行中可

19、能产生的错误进行有效的处理。应该在编写每个有可能抛出异常的函数时都应当加入异常规格说明。性的最有效方法之一C语言实现出错处理的方法需要特别注意的是:如果函数的执行错误所抛出的异常对象类型并未在函数的异常规格说明中声明,则会导致系统函数 unexpected() 被调用,以便解决未预见错误引起的异常。unexpected() 是由函数指针实现函数调用的,因此我们可通过改变函数指针所指向的函数执行代码的入口地址来改变相对应的处理操作。这就意味着用户定义自己特定的对未预见错误的处理方法(系统的缺省处理操作将最终导致程序终止运行)。实现自定义处理方法设定是调用系统函数 set_unexpected()

20、 完成的。性的最有效方法之一C语言实现出错处理的方法该函数的原型如下:typedef void (*unexpected_function)();unexpected_function set_unexpected( unexpected_function unexp_func );该函数可以将一个自定义的处理函数地址 unexp_func 设置为 unexpected 的函数指针新值,并返回该指针的当前值,以便保存,并用于恢复原处理方法。性的最有效方法之一C语言实现出错处理的方法例例9-29-2 展示了如何进行函数的异常规格说明和使用系统函数 set_unexpected() 处理未预见错误

21、。程序分析: 异常类型通常是小规模的(如本例的 up、fit),但 有时也需要异常类型包含大量的错误信息,使得异 常处理器可以依据这些信息确定恰当的解决方法。 函数 g() 有两个定义版本: 版本1不抛出异常,使得在函数 f() 中调用 g() 时 无异常抛出,这与 f() 的异常规格说明一致。 版本2有异常抛出,使得在函数 f() 中调用 g() 时 有异常抛出,违反了 f() 的异常规格说明。性的最有效方法之一C语言实现出错处理的方法 处理未预见异常的自定义函数 my_unexpected() 没有 返回值,但却可以抛出一个新异常(也可以抛出被 处理的未预见异常)或者直接调用系统函数 ex

22、it() 或 abort()。 使用 set_unexpected() 将 my_unexpected() 设置为系 统函数 unexpected 的函数指针新值,并将返回的指 针当前值保存在一个类型为 unexpected_function 的变 量中,以便恢复 unexpected 的函数指针的原值。 为了使程序能对所有的潜在异常进行检测,将测试 块 try 放入 for 循环中,这与前面讲到的使用异常处 理器的恢复模式很相似。性的最有效方法之一C语言实现出错处理的方法9.4.4 捕获所有的异常如果函数定义时没有异常规格说明,则在该函数被调用时就有可能抛出任何类型的异常对象。为了解决这个问

23、题,应该在异常处理器中增加一个能捕获任意类型的异常对象的处理分支。例如:catch() cout an unkown exception was thrown endl;性的最有效方法之一C语言实现出错处理的方法注意: 应将能捕获任意异常的处理分支放在异常处理器的 最后,避免遗漏对可预见异常的处理。 使用省略号 “” 做为 catch 的参数可以捕获所有异 常,但无法知道所捕获异常的类型。另外省略号不 能与其他异常类型同时作为 catch 的参数使用。性的最有效方法之一C语言实现出错处理的方法9.4.5 异常的重新抛出如果运行错误抛出的异常对象虽然被直接调用环境中的异常处理器捕获,但根据所获得

24、错误信息在当前运行环境中无法对异常进行恰当的处理,则需要将所捕获的异常对象从当前的运行环境重新抛向高一层运行环境,使产生异常的错误能在高一层运行环境中得到恰当的处理或将异常对象继续重新抛出。注意,如果在当前运行环境中没有捕获运行错误抛出的异常对象,则重新抛出异常对象的操作是自动发生的。性的最有效方法之一C语言实现出错处理的方法实现重新抛出异常的方法是在捕获异常对象的异常处理器分支中使用不带参数的 throw 语句。例如使用省略号做参数捕获任意类型异常对象时,由于无法得到有关异常的信息而将异常对象重新抛出:catch()cout an unkown exception was thrown en

25、dl;throw;由于每个被抛出异常对象在未被处理之前是被保留的,所以更高层次的运行环境的处理器总可以获得来自这个异常对象的完整信息。性的最有效方法之一C语言实现出错处理的方法9.4.6 未捕获的异常如果测试块 try 执行过程中抛出的异常对象在当前的异常处理器没有被捕获,则异常对象将进入更高一层的运行环境中。这种异常对象的抛出、捕获、处理过程按照运行环境的调用关系逐层进行,直到在某个层次的运行环境的异常处理器中捕获并恰当处理了异常对象才停止,否则将一直进行到调用系统的特定函数terminate() 终止程序的运行。例如,在异常对象的创建过程中、异常对象的被处理过程中或异常对象的析构过程中又抛

26、出了新异常对象,就会产生所抛出的异常对象不能被捕获。性的最有效方法之一C语言实现出错处理的方法注意: 特殊函数 terminate() 也是一个使用函数指针实现调 用的函数,因此允许用户定义自己特定的程序终止 函数。在 C 标准库中,terminate() 的函数指针的缺 省值是指向系统函数 abort() 的调用地址。abort() 的 功能是不调用正常的程序终止函数而直接从程序中 退出。 使用系统函数 set_terminate 来设定用户自定义的终 止函数作为 terminate() 函数的新执行函数,取代该 函数的当前执行函数,并可以通过 set_terminate 的 返回值获得 t

27、erminate() 的当前执行函数调用地址。性的最有效方法之一C语言实现出错处理的方法 用户的自定义终止函数除了必须不含输入参数,其 返回值必须为 void 外,不能抛出任何异常对象,但 可以调用一些程序终止函数,如 abort() 。在实际应用中,如果函数 terminate() 被调用,就意味着程序的运行错误已经无法被恢复。例例9-39-3 展示了异常对象的析构过程中抛出异常对象将导致程序终止函数 terminate() 被调用。为了能观察到 terminate() 被调用的情况,定义了一个自定义的终止函数,并使用 set_terminate 将其设定为 terminate() 的新执行

28、函数。性的最有效方法之一C语言实现出错处理的方法9.5 异常处理中对象的清除异常处理是否有效的关键就在于:当异常对象被抛出,程序从正常流程转入异常处理器的执行流程时,正常流程执行环境中所创建的对象除异常对象外必须被正确地清除。C+ 的异常处理器可以保证程序流程离开一个运行环境的作用域时,该作用域中所有结构完整的对象的析构函数将被调用,以确保清除这些对象。因此,如何保证对象的完整创建就成为需要特别关注的问题。性的最有效方法之一C语言实现出错处理的方法例例9-49-4 描述了由于在对象的创建过程中发生异常,使对象不能被完整创建,导致异常对象抛出时这些不完整的对象不能被清除。同时还展示了如果 une

29、xpected() 函数执行中再次抛出意外的异常对象时将会产生什麽结果。程序运行结果被记录在文本文件 cleanup.out 中,其内容如下:constructing noisy 0 name before arraynoisy:newconstructing noisy 1 name array elemconstructing noisy 2 name array elemconstructing noisy 3 name array elemconstructing noisy 4 name array elem性的最有效方法之一C语言实现出错处理的方法constructing nois

30、y 5 name array elemdestructing noisy 4 name array elemdestructing noisy 3 name array elemdestructing noisy 2 name array elemdestructing noisy 1 name array elemnoisy:deletedestructing noisy 0 name before arraycaught 5testing unexpected:constructing noisy 6 name before unexpectedconstructing noisy 7 n

31、ame zdestructing noisy 6 name before unexpectedcaught c分析上述结果不难看出:性的最有效方法之一C语言实现出错处理的方法 构造函数在两种情况下会发生异常抛出。 第一种情况是当第五个对象被创建时,被抛出的异 常对象是一个整数,该异常类型在构造函数的异常 规格说明中已经声明。 第二种情况是当参数字符串的第一个字符为“z”时将 抛出一字符型异常,由于该异常类型在构造函数的 异常规格说明中未声明,所以导致 unexpected() 被 调用。注意,如果用 catch(char c) 或 catch() 替换 catch(int c),则 unexp

32、ected() 将不会被调用。 性的最有效方法之一C语言实现出错处理的方法 第一种情况的异常发生时,第 0,1,2,3,4 个对 象都已被完整创建,所以异常处理器将调用它们的 析构函数清除它们。而第 5 个对象在创建中抛出了 异常对象,所以它没有被完整创建,因此异常处理 器不能调用它的析构函数清除它。 第二种情况的异常发生时,第 6 个对象都已被完整 创建,所以异常处理器将调用它的析构函数清除 它。而第 7 个对象在创建中抛出了构造函数的异常 规格说明中未声明的异常,导致 unexpected() 被调 用,所以该对象没有被完整创建,因此异常处理器 不能调用它的析构函数清除它。性的最有效方法之

33、一C语言实现出错处理的方法 异常发生时还没有被创建的对象(第一种情况时的 对象 array5,array6 和 n2 以及第二种情况时的对 象 n5 )均不再被创建。 对于类 noisy,运算符 new 和 delete 均被重载。 函数 unexpected_rethrow 可抛出所有类的异常,所以 该函数再次抛出与已知类型完全相同的异常。这样 函数 unexpected_rethrow 的作用就是接收任何未加说 明的异常对象,并作为已知异常对象再次抛出。通 过这种方法,该函数可作为滤波器用以跟踪意外异 常的出现并获取该异常对象的类型。性的最有效方法之一C语言实现出错处理的方法9.6 构造函

34、数从上一章的讨论可知,如果构造函数中出现异常,这将使得类对象不能被完整创建,从而导致类对象需要撤消时析构函数不能被正常调用,使类对象无法被撤消。这意味着在编写类的构造函数时,必须十分慎重地对待是否会有异常发生。另一方面,构造函数进行存储资源的动态分配是经常发生的,而在存储资源的动态分配过程中可能有异常发生。如果用于动态分配存储资源的指针是未加保护的,则析构函数将无法收回这些存储资源。性的最有效方法之一C语言实现出错处理的方法例例9-59-5 描述了在对象构造过程中出现异常引起的错误运行结果产生的文件 nudep.out 中保存了如下信息:useResources()bonk()bonk()bo

35、nk()allocating an oginside handler分析结果不难看出:性的最有效方法之一C语言实现出错处理的方法类 useResources 的构造函数执行过程中,当通过指针属性 bp 动态分配 bonk 类型数组空间时, bonk 类型的构造函数被调用不会抛出异常对象。而当通过指针属性 op 动态分配 og 类型对象时,则 og:operator new 的执行中会抛出一个异常对象。这就使得程序在异常处理器中被意外地结束,而 useResources 的析构函数未能得到调用。这是因为异常发生时,useResources 构造函数的全部工作没未完成,使得 useResource

36、s 对象所有的内存空间,包括已经被完整创建的 bonk 类型的数组空间都不能被清除。性的最有效方法之一C语言实现出错处理的方法内存动态分配操作的对象化:为了防止上述情况的发生,应避免对象通过本身的构造函数将 “不完整” 的资源分配到对象中。所谓对象化方法是将每个分配操作变成了 “原子型” 的,像一个类对象,只要是成功分配资源的 “原子型” 对象都能被正确地清除。模板是实现内存动态分配操作对象化的一种好方法。例例9-69-6 展示了对例9-5 进行内存动态分配操作对象化修改所产生的结果。运行结果产生的文件 wrapped.out 中保存了如下信息:性的最有效方法之一C语言实现出错处理的方法bon

37、k()bonk()bonk()pwrap constructorallocating an ogbonk()bonk()bonk()pwrap destructorinside handler性的最有效方法之一C语言实现出错处理的方法分析结果:不同点是使用类模板 pwrap 封装内存资源的动态分配操作,并使用 pwrap 实例定义 useResources 的对象成员 Bonk 和 Og,替代原来的 bonk 类型指针 bp 和 og 类型指针 op。在 useResources 对象的构造过程中,模板 pwrap 的构造函数被调用,如果这些模板构造函数所进行的内存资源的动态分配操作完成之后发

38、生了异常,已分配内存资源就能够被模板析构函数清除。因此,对象成员 Og 创建时,存储空间分配操作所发生的异常不会影响为 Bonk 对象数组动态分配的内存空间被清除,没有内存泄漏。性的最有效方法之一C语言实现出错处理的方法9.7 异常对象的匹配当一个异常对象抛出时,异常处理器会根据被抛出异常对象的类型顺序匹配 “最近” 的异常处理分支。异常对象的匹配并不要求被抛出的异常对象的类型和异常处理分支的参数类型完全一致。如果一个被抛出的异常对象的类型是某个类的派生类,则在异常处理器中允许用一个使用基类为参数的异常处理分支与其匹配。但需要注意的是:性的最有效方法之一C语言实现出错处理的方法 若处理器的参数

39、是基类对象,而不是基类对象的引 用或指针,派生类异常对象在与异常处理器匹配的 过程中将会被 “切片”,被 “切片” 的异常对象虽然不会受到破坏,但会丢失所有派生类型的新增特征。 系统预定义类型的异常对象在匹配过程中,可以发 生类型的隐含转换,而自定义类型的异常对象在匹 配过程中,不会发生类型的隐含转换。例例9-79-7 描述了在自定义类型的异常对象匹配过程中不会自动执行类型的转换。性的最有效方法之一C语言实现出错处理的方法分析结果:尽管我们已经通过类 except2 的构造函数提供了将类except1 对象隐含转换为 except2 对象的功能,但是当except1 对象作为异常对象被抛出后,

40、在与异常处理器的第一个异常处理分支匹配过程时,不会被隐含转换为 except2 对象。例例9-89-8 描述了在异常处理器中如何使用基类参数捕获派生类的异常对象。性的最有效方法之一C语言实现出错处理的方法分析结果: 第一个异常处理分支总能匹配一个 trouble 对象或从 trouble 派生的类对象; 由于第一个异常处理分支捕获了能与第二和第三个 异常处理分支的所有异常对象,导致第二和第三个 异常处理分支永远不会被调用。 异常处理器中的异常对象匹配顺序应该先匹配派生 类异常,而把基类异常的匹配放在最后更有意义; 由于派生类 small 和 big 的对象比基类 trouble 的对象 大,因

41、此在第一个异常处理分支中它们将被 “切片” 以适应异常对象匹配的需要。避免这种信息的剪裁 应该使用基类的引用或指针作为匹配参数。性的最有效方法之一C语言实现出错处理的方法9.8 标准异常在 C+ 标准库中提供了一批标准异常类,为用户在编程中直接使用和作为派生异常类的基类。下面的三张表描述了这些标准异常类:类名说明头文件exception是所有标准异常类的基类。可以调用它的成员函数 what() 获取其特征的显示说明。exceptionlogic_errorexception 的派生类,报告程序逻辑错误,这些错误在程序执行前可以被检测到。stdexceptruntime_errorexcepti

42、on 的派生类,报告程序运行错误,这些错误仅在程序运行时可以被检测到。stdexceptios_base:failure exception 的派生类,报告 I/O 操作错误,ios_base:clear() 可能抛出该异常类对象。xiosbase性的最有效方法之一C语言实现出错处理的方法类名说明(logic_error 的派生类)头文件domain_error报告超越作用域的错误。stdexceptinvalid_argument 报告函数有一个无效参数。stdexceptlength_error报告试图创建超过类型尺寸 size_t 最大值可以表示的对象。stdexceptout_of_r

43、ange报告参数值越界stdexceptbad_case报告在运行时类型识别中有一个无效的dynamic_cast 表达式。typeinfobad_typeid报告表达式 typeid(*p) 中指针 p 无效。typeinfo性的最有效方法之一C语言实现出错处理的方法类名说明(runtime_error 派生类)头文件range_error报告违反了后置条件。stdexceptoverflow_error报告一个算术上溢。stdexceptunderflow_error报告一个算术下溢。stdexceptbad_alloc报告一个存储分配错误。new性的最有效方法之一C语言实现出错处理的方法

44、其中异常基类 exception 的定义如下:class _CRTIMP exception public: exception(); exception(const _exString&); exception(const exception&); exception& operator= (const exception&); virtual exception(); virtual _exString what() const;private: _exString _m_what; int _m_doFree;它的派生类,以 logic_error 为例,其定义如下:性的最有效方法之一C

45、语言实现出错处理的方法class _CRTIMP logic_error : public exception public:explicit logic_error(const string& _S): exception(), _Str(_S) virtual logic_error() virtual const char *what() const return (_Str.c_str(); protected:virtual void _Doraise() const_RAISE(*this); private:string _Str;性的最有效方法之一C语言实现出错处理的方法9.9

46、 含有异常的程序设计在本章中将论述使用异常处理机制和方法进行程序设计的一些常用原则。9.9.1 何时避免使用异常处理异常处理并不是解决所有运行错误的的最佳方法。下面是各种不适合使用异常处理的情况:1 异步事件标准 C 的信号系统 signal 以及其他类似的控制系统产生的异步事件。 异步事件发生在程序控制的范围以外,它必须有 完全独立的代码来处理,而不是程序流的一部 分,它的发生是程序所不能预计的。性的最有效方法之一C语言实现出错处理的方法 异常的发生和异常的处理处于相同的运行环境 中,即异常被限制在一定范围内。 在一些定义明确的程序点上,一个异常可以以基 于中断的方式抛出。2 普通错误情况如

47、果一个错误发生时有足够的信息去处理它,这个错误就应引起一个异常。此时应该关心当前运行环境,而不应该创建一个描述错误的异常对象,并将它抛出当前的运行环境。例如,不应当为机器层的事件(如 “除零溢出”)抛出异常,这些异常可由其他机制(如操作系统或硬件)去处理。性的最有效方法之一C语言实现出错处理的方法3 流控制虽然异常和处理看上去有点像一个交替返回机制,也有点像一个 switch 语句段,但我们不能用这种机制去控制程序流,而改变了使用该机制的初衷。4 不强迫使用异常 一些程序相当简单,例如一些应用程序可能仅仅需要获取输入和执行一些加工。如果在这类程序中试图分配存储或使用文件操作等,则可以使用 as

48、sert() 显示出错信息,使用 abort() 终止程序。而刻意使用异常处理方法进行异常对象的创建、抛出、捕获来实现系统资源修复,则是不明智的。从根本上说,假如不需要使用异常,就不要使用。性的最有效方法之一C语言实现出错处理的方法9.9.2 异常处理的典型使用使用异常处理的优点主要表现在: 便于将问题固定下来和重新测试调用这个导致异常 的函数。 便于修正由异常对象指示的错误,而后继续运行。 便于计算一些选择结果用于代替函数假定产生的结 果。 便于在当前运行环境中尽其所能解决运行错误,并 且便于将不能解决的错误抛出的异常对象抛向更高 一层的运行环境。性的最有效方法之一C语言实现出错处理的方法

49、便于根据不能解决的错误抛出的异常对象,创建一 个新的相关的异常对象,并抛向更高一层的运行环 境。 便于终止程序运行。 便于包装使用普通错误处理函数(尤其是 C 的库函 数),以便产生异常处理替代。 便于简化出错处理。 便于更安全地使用库和程序。性的最有效方法之一C语言实现出错处理的方法在程序设计中使用异常处理机制应该注意:1 在函数声明中使用异常规格说明 函数原型声明中的异常规格说明是用户如何使用 异常处理机制编写函数调用,处理函数可能发生 的运行错误的程序代码和编译器编译这些代码的 依据。 当函数运行期间,由于不同的原因抛出了函数的 异常规格说明没有声明的异常类型对象,则会导 致对 unex

50、pected() 的调用。因此,在使用异常规格 说明函数可能抛出的异常对象类型的同时,一般 也应定义用户定制的 unexpected() 函数执行代码。性的最有效方法之一C语言实现出错处理的方法2 基于标准异常类 C+ 的标准异常类应该成为用户在编写函数的异 常规格说明时的首选异常类型。 假若 C+ 的标准异常类不能满足用户编程需要, 则应该尽量从某个已存在的标准异常类派生定义 用户自己的异常形成。特别是使用 exception 作为 用户派生异常类的基类,重新定义该类的虚成员 函数 what() ,这会使用户受益匪浅。性的最有效方法之一C语言实现出错处理的方法3 套装我们自己的异常如果为我们

51、的特定类创建异常,在类中套装异常类是一个好主意,这些异常类仅为我们的特定类使用。还可以防止命名域的混乱。4 使用异常层次异常层次为不同类型的重要错误分类提供了一个有价值的方法,这些错误可能会与我们的类或库冲突。异常层次可为用户提供有帮助的信息,帮助用户组织自己的代码,选择是忽略所有异常特定类型还是正确地捕获基类类型。异常层次使得任何异常可通过对相同基类的继承而追加,以基类为参数的异常处理器将捕获新的异常。C+ 的标准异常类是一个异常层次的优秀范例。性的最有效方法之一C语言实现出错处理的方法5 多重继承当需要把一个指向对象的指针向上映射到两个不同的基类,实现两个基类的多态行为时,使用多重继承将是

52、十分有效的。多重继承对于异常层次也是非常有用的,因为以多重继承异常的任一个基类为参数的异常处理器都可以处理多重继承异常。6 用异常 “引用” 而非异常 “值” 去捕获如果抛出一个派生类对象而且该对象被基类的异常处理器通过 “值” 捕获到,对象会被 “切片”,也就是说,随着向基类对象的传递,派生类元素会被依次割下,直到传递完成。性的最有效方法之一C语言实现出错处理的方法例例9-99-9 展示了使用 “引用” 将一个异常的派生类对象传递给基类处理器时不被 “切片” 的实例。 当派生类 derived 对象通过 “值” 被捕获时,被 “切 片” 成基类 base 对象,表现出 base 对象的行为。

53、 当派生类 derived 对象的 “引用” 被捕获时,对象 不会被 “切片”,表现出派生类的真实情况。 虽然也可以抛出和捕获指针,但这样做会引入更 多的耦合 抛出和捕获器必须为怎样分配和清 除异常对象达成一致,这将是一个问题,因为新 的异常又可能会由于堆的耗尽而产生。性的最有效方法之一C语言实现出错处理的方法7 在构造函数中抛出异常由于构造函数没有返回值,如果没有异常机制,只能按以下两种选择报告在构造期间的错误: 设置一个非局部的标志并希望用户检查它。 希望用户检查对象是否被完全创建。 这是一个严重的问题,因为在 C+ 程序中,对象构造失败后继续执行注定是灾难。所以构造函数成为抛出异常最重要

54、的用途之一。使用异常机制是处理构造函数错误的安全有效的方法。然而我们还必须把注意力集中在对象内部的指针上和构造函数异常抛出时的清除方法上。性的最有效方法之一C语言实现出错处理的方法8 不要在析构函数中抛出异常由于析构函数会在抛出异常时被调用,所以永远不要在析构函数中抛出一个异常或者通过执行在析构函数中的动作导致其他异常的抛出。否则就意味着在已存在的异常到达引起捕获之前又抛出一个新的异常,这会导致对 terminate() 的调用。换句话说,假若调用一个析构函数中的任何函数都有可能会抛出异常,则这些调用应该写在析构函数中的一个测试 块 try 中,而且析构函数必须自己处理所有自身的异常,即这里的

55、异常都不应逃离析构函数内部。性的最有效方法之一C语言实现出错处理的方法9.10 开销使用任何一个新特性必然有所开销。异常被抛出需要开销相当的运行时间,这就是不要把异常处理用于程序流控制的一部分原因。相对于程序的正常执行,异常是偶而发生的。因此设计异常处理的重要目标之一是:当异常没有发生时,异常处理代码应不影响运行速度。换句话说,只要不抛出异常,代码的运行速度如同没有添加异常处理代码时一样。这是因为异常处理代码的编译都依赖于使用特定的编译器。异常处理也会引出额外信息(空间开销),这些信息被编译器置于栈上。性的最有效方法之一C语言实现出错处理的方法9.11 小结错误的处理和恢复是和我们编写每个程序

56、都密切相关的基本原则,在 C+ 中尤其重要,创建程序组件为其他人重用是开发的目标之一。为了创建一个稳固系统,必须使每个组件具有健壮性。C+ 中异常处理的目标是简化大型可靠程序的创建,使用尽可能少的代码,使应用中没有不受控制的错误。异常处理几乎不损害性能,并且对其他代码的影响很小。基本异常并不难于掌握,应该在程序中尽量地使用它们。性的最有效方法之一C语言实现出错处理的方法9.12 Java 的异常处理Java 的异常处理在实现机制上与 C+ 的异常处理是一致的,但在具体的实现方法方面,二者之间还是存在着一些重要的区别:1 从理论上说,在 C+ 中任何类型的对象都可以作为异常对象被抛出,但在 Ja

57、va 中可以作为异常抛出对象的类型必须是 Throwable 类的子类。按照约定,用户定义的异常属于 Exception 类型,它是 Thrwoable 的一个子类,例如:class MyException extends Exception 从一定意义上说,Java 的异常类型定义比 C+ 更加方便和安全,但不如 C+ 灵活。性的最有效方法之一C语言实现出错处理的方法2 在 Java 中,如果一个函数会抛出一个异常,它必须在函数定义的首部使用关键字 throws 予以声明。例如:void f(int j) throws MyException / 允许声明抛出多个异常throw new My

58、Exception( ); 换句话说,如果没有抛出异常的声明,则表明该函数不会抛出异常。这一点与 C+ 对函数的异常规格说明有着很大的不同。性的最有效方法之一C语言实现出错处理的方法3 在 Java 中,一个声明会抛出异常的函数只能按照以下几种模式之一进行调用: 必须在一个 try catch 代码块中调用,并且抛出 的异常必须被捕获。 调用函数也可以重新抛出异常,该异常可以在其 他地方被捕获(包括由系统提供的缺省异常处理 函数)。 也可以同时提供上述两种模式调用。 4Java 的 catch 代码块中必须显式地指定一个参数,即便这个 catch 代码块定义并未使用这个参数。例如:catch(

59、 MyException e ) 性的最有效方法之一C语言实现出错处理的方法5Java 中异常处理的通用语句如下:try catch( exception_type_1 identifier_1 ) catch( exception_type_2 identifier_2 )catch( exception_type_3 identifier_3 ) finally 性的最有效方法之一C语言实现出错处理的方法如果 try 代码块中的语句都执行成功,控制流直接从 try 代码块转移到 finally 代码块,并接续此代码块之后的代码继续执行;否则 try 语句执行中抛出的异常将按照 catch 代码块出现的顺序与每个 catch 代码块的参数进行匹配尝试,只要有一个匹配,剩下的 catch 代码块就不再进行匹配尝试了。匹配的 catch 代码块执行完毕后,程序的控制流将转移到 finally 代码块(如果存在),但 finally 代码块之后的代码不再执行。性的最有效方法之一C语言实现出错处理的方法

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 办公文档 > 工作计划

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号