




已阅读5页,还剩72页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第九章 异常处理,程序的错误有两大类: 编译链接错误:这类错误是由程序的语法错误(例 如关键字错误、变量未定义、语句结束缺分号、括 号失配、结构失配等)和其他错误(函数只声明未 定义、缺少库的链接配置等)引起的。这类程序错 误发生在程序的编译链接过程中,对于一个具有一 定经验的编程人员是容易解决的。 运行错误:这类程序错误发生在程序的运行期间, 主要表现在计算过程中的被0除、内存空间不足、数 据的输入输出错误等。这类程序错误只靠编程人员 的经验是难以避免的。,错误修复技术是解决程序运行错误,提高代码健壮 性的最有效方法之一。C 语言实现出错处理的方法是 出错与错误处理的紧耦合,即检查被调函数的返回值 或输出信息,以便确定是否发生错误,作出相应的处 理。这种出错处理存在两个主要问题: 出错处理的繁琐和错误检查引起的代码膨胀将不可避 免地降低程序的执行效率,增加程序的阅读困难。 被调用函数只清楚出错原因而不清楚被调用环境,因 此缺乏处理错误的依据。因此这种将用户函数与出 错处理紧密结合的方法将造成使用出错处理的不方 便和难以接受。,正是因为上述原因,使得不少程序设计人员在实际 设计中常常 “忽略” 出错处理,似乎是在 “不会出错” 的 状态下编程,这会严重地降低程序代码的健壮性。 异常处理是 C+ 语言的一个重要特征,它提出了出 错处理更加完美的方法。 出错处理代码的编写不再繁琐,也不须将出错处理代 码与功能代码紧密结合。在可能发生错误的函数中 加入出错代码,并在后面调用该函数的程序中加入 错误处理代码。如果程序中多次调用一个函数,可以在程序中加入一个专门用于被调函数的出错处理 函数。, 错误发生是不会被忽略的。如果被调用函数需发送 一条出错信息给调用函数,它可向调用环境发送一 个描述错误信息的对象。如果调用环境没有捕获该 错误信息对象,则该错误信息对象会被自动向上一 层的调用环境发送;如果调用环境无法处理该错误 信息对象,则调用环境可以将该错误信息对象主动 发送到上一层的调用环境中;直到该错误信息对象 被捕捉和处理。,9.1 C 语言的出错处理 在通过对被调用函数的返回或对断言宏 assert() 的判 断结果的检查能够确切定后续操作的情况下,出错处 理就变得十分明确和容易了,因为可以通过程序执行 的当前运行环境得到所有必要的信息。然而能够这样 处理的错误都是环境一般都是简单的普通错误。 如果错误问题发生时,在程序当前运行环境中无法 获得足够的错误发生和处理的信息,则需要从更大的 运行环境中获取出错处理信息。C 语言处理这类错误 情况的典型方法有三种:, 出错信息可以通过函数的返回值获得。如果返回值 不足以描述出错信息,则可设置全局错误判断标志 (标准 C 语言中全局变量 errno 以及系统运行库函 数 perror(const char *string) 、strerror(int errnum) 支持这 一方法)。由于这种方法要对每个函数调用都进行错 误检查,这将十分繁琐并增加程序的混乱度。另 外,偶然出现异常的函数返回值可能并不反映什麽 问题。, 可使用 C 信号处理库中的 signal 函数设置中断信号 处理,和使用 raise 函数向正在运行的程序发送信 号。这两个函数的原型如下: void(*signal(int sig, void(_cdecl *func)(int sig, int subcode) ) (int sig); int raise( int sig ); 信号处理库的使用者必须清楚地了解、恰当地定义 和设置中断信号处理。同时对于大型项目,不同库 之间的信号可能会产生冲突。因此,信号处理库的 使用有一定的难度。, 使用 C 标准库中的设置跳转函数 setjmp 和非局部跳 转函数 longjmp 实现出错和错误处理。这两个函数 的原型如下: setjmp(jmp_buf env); longjmp(jmp_buf env, int value); 调用 setjmp 函数在程序中存储一典型的正常状态, 如果进入错误状态,longjmp 可恢复由 setjmp 函数所 设定的状态,并且状态被恢复时的存储地点与错误 发生地点紧密联系。,在较早的 Visual C+ 版本(例如 VC+ 6.0)中 C 语 言的信号处理技术和 setjmp/longjmp 函数被调用时不能 正确地调用类对象的析构函数,所以一个描述出错信 息的对象不能被正确地清除。如果出错对象不能被清 除,则该对象将被保留下来且不能再次被正确地存 取,因此,实际上是不可能有效、正确地从异常情况 中恢复。所以在 C+ 编程中不推荐使用 C 语言中这种 处理出错的方法。 例9-1 描述了 setjmp/longjmp 函数的这一特点。,在 VC+ 6.0 中的运行结果: tornado, witch, munchkins. theres no place like home theres no place like home theres no place like home rainbow() is called. Auntie Em! I had the strangest dream. 分析: 由于 main 函数中 setjmp 的调用(保存当前运行点环 境)后返回 0,导致 if 分支的执行,显示了前 5 行 执行信息。, 全局函数 OZ 被调用,其中 longjmp 的调用结果使得 程序的运行恢复到由 setjmp 保存的运行点,导致使 setjmp 再次被调用。由于保存运行点环境的缓冲区 kansas 被 setjmp 的第一次调用设置,使得 setjmp 的 第二次调用返回值为非 0,导致 else 分支执行,显 示第 6 行运行信息,但 rainbow 对象 RB 未被析构。 结论: 程序中调用 setjmp 和 longjmp 的方法提供了修复程 序运行错误的典型结构和方法。 使用 setjmp 和 longjmp 可能无法析构错误发生前创 建的对象,不能满足面向对象程序的错误处理。,9.2 C+ 语言的出错处理 虽然 C 语言的出错处理技术和方法在 C+ 语言的程 序设计中仍然可以使用,但更重要的是 C+ 提供了一 套符合面向对象程序设计的出错处理技术 异常处 理技术。异常处理的特点主要表现在: 异常的产生和异常的处理分离。 异常的信息数据和行为被封装成独立的类对象。 结构化的异常处理为异常对象的捕获、处理、传递 提供了有效、方便的编程手段。 能确保残留对象在异常处理时被彻底析构。,9.3 异常对象的创建和抛出 如果程序发生了异常情况,而在当前运行环境中无 法获取处理异常的足够信息,就可以创建一个包含异 常信息和行为的对象,并将该对象从发生异常的运行 环境中抛出,发送到上一层运行环境中,这称为异常 的创建和抛出,例如: throw myerror(“something bad happened“); 分析: 关键字 throw 的作用是抛出异常对象,被抛出的对象可以是包括系统预定义类型在内的任何类型,当然多数情况为自定义异常类型,如本例中的 myerror 就是一个以字符串为参数创建的自定义异常对象。, 在函数中使用关键字 throw 抛出异常类对象都是函 数正常执行中不存在的。抛出异常类对象的结果是 导致函数退出执行,但不是函数设计的正常返回。 因此,异常类型可以视为是函数异常退出作用域的 返回值类型。 函数的正常返回和对函数返回的检查和处理是处于 同一作用域的,而对异常的处理作用域与异常抛出 作用域可能相距很远。注意,只有完整创建的异常 对象才能在异常被处理时被清除,而函数返回(包 括异常返回)时,函数作用域内的所有对象均被清 除。, 函数可以根据错误发生的原因不同,抛出不同类型 的异常对象,使函数的调用者可以在更大范围的程 序上下文环境中考虑对异常的处理。,9.4 异常的捕获和处理 函数在发生错误时能以抛出异常对象的方式结束函 数执行是建立在假定该异常对象能被捕获和处理的前 提下的。这一假定在 C+ 中是成立的,这也是异常处 理的一个优点。完成函数调用时的异常测试,异常对 象的捕获和处理是由 try - catch 结构实现的,使得处理 程序运行错误的编码变得方便、有效,并具有完全的 结构化和良好的可读性。该结构的一般形式如下: try 被测试的程序代码 catch(异常类型 异常对象名) 异常处理的程序代码 ,9.4.1 测试块 try 测试块 try 的作用是使处于该块中的程序代码执行可 能抛出的异常对象能在后续的异常处理器中被捕获, 从而确定如何处理。因此,调用一个函数,并期望在 函数调用者所在程序运行环境中使用异常处理的方法 解决函数可能发生的错误,就必须将函数调用语句置 于测试块 try 中。否则函数所抛出的异常对象就不能被 后续的异常处理器捕获,从而使异常对象被自动传递 到上一层运行环境,直至被操作系统捕获和处理,导 致程序被终止执行。,9.4.2 异常处理器 异常发生后,被抛出的异常对象一旦被随后的异常 处理器捕获到,就可以被处理。根据在当前运行环境 中能否解决引起异常的程序运行错误,对异常对象的 处理有两种: 尝试解决程序运行错误,析构异常对象。 无法解决程序运行错误,将异常对象抛向上一层运 行环境。 为此,异常处理器应该具备捕获一个以上任何类型 的异常对象的能力,每个异常对象的捕获和处理由关 键字 catch 引导。 例如:,try / code that may generate exception catch(type1 id1) / handle exceptions of type1 catch(type2 id2) / handle exceptions of type2 / etc, 每个 catch 语句相当于一个以特定的异常类型为单 一参数的小型函数; 标识符 id1、id2 等如同函数中的参数名,如果对引 起该异常对象抛出的程序运行的错误处理中无须使 用异常对象,则该标识符可省略; 异常处理器部分必须紧跟在测试块 try 之后; catch 语句与 switch 语句不同,即每个 case(情况) 引起的执行需要加入 break 实现执行的结束; 测试块 try 中不同函数的调用可能会抛出相同的异常 对象,而异常处理器中对同一异常对象的处理方法 只需要一个。,异常处理的两种模式 终止与恢复: 终止模式 如果引起异常的是致命错误,即表明程序运行进入 了无法恢复正常运行的状态,这时必须调用终止模 式结束程序运行的异常状态,而不应返回异常抛出 之处。 恢复模式 恢复意味着期望对异常的处理能够修复异常状态, 然后再次对抛出异常对象的函数进行测试调用,使 之能够成功运行。,如果希望程序具有恢复运行的能力,就需要程序在 异常处理后仍能继续正常执行,这时异常处理就更 像一个被调用的函数。在程序需要进行恢复运行的 地方,可以将测试块 try 和异常处理器放在 while 循 环中,直到测试调用得到满意的结果。,9.4.3 异常规格说明 编写异常处理器必须知道被测试调用的函数能抛出 哪些类型的异常对象。C+ 提供了异常规格说明语 法,即在函数原型声明中,位于参数表列之后,清晰 地告诉函数的使用者:该函数可能抛出的异常类型, 以便使用者能够方便地捕获异常对象进行异常处理。 带有异常规格说明的函数原型说明的一般形式: 返回类型 函数名(参数表列) throw (异常类型名, ),使用异常规格说明的函数原型有三种: 抛出指定类型异常对象的函数原型: void function() throw(toobig, toosmall, divzero); 能抛出任何类型异常对象的函数原型: void function(); 注意,该形式与传统的函数原型声明形式相同。 不抛出任何异常对象的函数原型: void function() throw(); 为了实现对函数的安全调用和对函数执行中可能产 生的错误进行有效的处理。应该在编写每个有可能抛 出异常的函数时都应当加入异常规格说明。,需要特别注意的是:如果函数的执行错误所抛出的 异常对象类型并未在函数的异常规格说明中声明,则 会导致系统函数 unexpected() 被调用,以便解决未预见 错误引起的异常。 unexpected() 是由函数指针实现函数调用的,因此我 们可通过改变函数指针所指向的函数执行代码的入口 地址来改变相对应的处理操作。这就意味着用户定义 自己特定的对未预见错误的处理方法(系统的缺省处 理操作将最终导致程序终止运行)。实现自定义处理 方法设定是调用系统函数 set_unexpected() 完成的。,该函数的原型如下: typedef void (*unexpected_function)(); unexpected_function set_unexpected( unexpected_function unexp_func ); 该函数可以将一个自定义的处理函数地址 unexp_func 设置为 unexpected 的函数指针新值,并返回该指针的 当前值,以便保存,并用于恢复原处理方法。,例9-2 展示了如何进行函数的异常规格说明和使用系 统函数 set_unexpected() 处理未预见错误。 程序分析: 异常类型通常是小规模的(如本例的 up、fit),但 有时也需要异常类型包含大量的错误信息,使得异 常处理器可以依据这些信息确定恰当的解决方法。 函数 g() 有两个定义版本: 版本1不抛出异常,使得在函数 f() 中调用 g() 时 无异常抛出,这与 f() 的异常规格说明一致。 版本2有异常抛出,使得在函数 f() 中调用 g() 时 有异常抛出,违反了 f() 的异常规格说明。, 处理未预见异常的自定义函数 my_unexpected() 没有 返回值,但却可以抛出一个新异常(也可以抛出被 处理的未预见异常)或者直接调用系统函数 exit() 或 abort()。 使用 set_unexpected() 将 my_unexpected() 设置为系 统函数 unexpected 的函数指针新值,并将返回的指 针当前值保存在一个类型为 unexpected_function 的变 量中,以便恢复 unexpected 的函数指针的原值。 为了使程序能对所有的潜在异常进行检测,将测试 块 try 放入 for 循环中,这与前面讲到的使用异常处 理器的恢复模式很相似。,9.4.4 捕获所有的异常 如果函数定义时没有异常规格说明,则在该函数被 调用时就有可能抛出任何类型的异常对象。为了解决 这个问题,应该在异常处理器中增加一个能捕获任意 类型的异常对象的处理分支。例如: catch() cout “an unkown exception was thrown endl; ,注意: 应将能捕获任意异常的处理分支放在异常处理器的 最后,避免遗漏对可预见异常的处理。 使用省略号 “” 做为 catch 的参数可以捕获所有异 常,但无法知道所捕获异常的类型。另外省略号不 能与其他异常类型同时作为 catch 的参数使用。,9.4.5 异常的重新抛出 如果运行错误抛出的异常对象虽然被直接调用环境 中的异常处理器捕获,但根据所获得错误信息在当前 运行环境中无法对异常进行恰当的处理,则需要将所 捕获的异常对象从当前的运行环境重新抛向高一层运 行环境,使产生异常的错误能在高一层运行环境中得 到恰当的处理或将异常对象继续重新抛出。注意,如 果在当前运行环境中没有捕获运行错误抛出的异常对 象,则重新抛出异常对象的操作是自动发生的。,实现重新抛出异常的方法是在捕获异常对象的异常 处理器分支中使用不带参数的 throw 语句。例如使用省 略号做参数捕获任意类型异常对象时,由于无法得到 有关异常的信息而将异常对象重新抛出: catch() cout “an unkown exception was thrown“ endl; throw; 由于每个被抛出异常对象在未被处理之前是被保留 的,所以更高层次的运行环境的处理器总可以获得来 自这个异常对象的完整信息。,9.4.6 未捕获的异常 如果测试块 try 执行过程中抛出的异常对象在当前的 异常处理器没有被捕获,则异常对象将进入更高一层 的运行环境中。这种异常对象的抛出、捕获、处理过 程按照运行环境的调用关系逐层进行,直到在某个层 次的运行环境的异常处理器中捕获并恰当处理了异常 对象才停止,否则将一直进行到调用系统的特定函数 terminate() 终止程序的运行。例如,在异常对象的创建 过程中、异常对象的被处理过程中或异常对象的析构 过程中又抛出了新异常对象,就会产生所抛出的异常 对象不能被捕获。,注意: 特殊函数 terminate() 也是一个使用函数指针实现调 用的函数,因此允许用户定义自己特定的程序终止 函数。在 C 标准库中,terminate() 的函数指针的缺 省值是指向系统函数 abort() 的调用地址。abort() 的 功能是不调用正常的程序终止函数而直接从程序中 退出。 使用系统函数 set_terminate 来设定用户自定义的终 止函数作为 terminate() 函数的新执行函数,取代该 函数的当前执行函数,并可以通过 set_terminate 的 返回值获得 terminate() 的当前执行函数调用地址。, 用户的自定义终止函数除了必须不含输入参数,其 返回值必须为 void 外,不能抛出任何异常对象,但 可以调用一些程序终止函数,如 abort() 。 在实际应用中,如果函数 terminate() 被调用,就意味 着程序的运行错误已经无法被恢复。 例9-3 展示了异常对象的析构过程中抛出异常对象将 导致程序终止函数 terminate() 被调用。为了能观 察到 terminate() 被调用的情况,定义了一个自定 义的终止函数,并使用 set_terminate 将其设定为 terminate() 的新执行函数。,9.5 异常处理中对象的清除 异常处理是否有效的关键就在于:当异常对象被抛 出,程序从正常流程转入异常处理器的执行流程时, 正常流程执行环境中所创建的对象除异常对象外必须 被正确地清除。 C+ 的异常处理器可以保证程序流程离开一个运行 环境的作用域时,该作用域中所有结构完整的对象的 析构函数将被调用,以确保清除这些对象。因此,如 何保证对象的完整创建就成为需要特别关注的问题。,例9-4 描述了由于在对象的创建过程中发生异常,使对 象不能被完整创建,导致异常对象抛出时这些不 完整的对象不能被清除。同时还展示了如果 unexpected() 函数执行中再次抛出意外的异常对 象时将会产生什麽结果。 程序运行结果被记录在文本文件 cleanup.out 中,其内 容如下: constructing noisy 0 name before array noisy:new constructing noisy 1 name array elem constructing noisy 2 name array elem constructing noisy 3 name array elem constructing noisy 4 name array elem,constructing noisy 5 name array elem destructing noisy 4 name array elem destructing noisy 3 name array elem destructing noisy 2 name array elem destructing noisy 1 name array elem noisy:delete destructing noisy 0 name before array caught 5 testing unexpected: constructing noisy 6 name before unexpected constructing noisy 7 name z destructing noisy 6 name before unexpected caught c 分析上述结果不难看出:, 构造函数在两种情况下会发生异常抛出。 第一种情况是当第五个对象被创建时,被抛出的异 常对象是一个整数,该异常类型在构造函数的异常 规格说明中已经声明。 第二种情况是当参数字符串的第一个字符为“z”时将 抛出一字符型异常,由于该异常类型在构造函数的 异常规格说明中未声明,所以导致 unexpected() 被 调用。注意,如果用 catch(char c) 或 catch() 替换 catch(int c),则 unexpected() 将不会被调用。, 第一种情况的异常发生时,第 0,1,2,3,4 个对 象都已被完整创建,所以异常处理器将调用它们的 析构函数清除它们。而第 5 个对象在创建中抛出了 异常对象,所以它没有被完整创建,因此异常处理 器不能调用它的析构函数清除它。 第二种情况的异常发生时,第 6 个对象都已被完整 创建,所以异常处理器将调用它的析构函数清除 它。而第 7 个对象在创建中抛出了构造函数的异常 规格说明中未声明的异常,导致 unexpected() 被调 用,所以该对象没有被完整创建,因此异常处理器 不能调用它的析构函数清除它。, 异常发生时还没有被创建的对象(第一种情况时的 对象 array5,array6 和 n2 以及第二种情况时的对 象 n5 )均不再被创建。 对于类 noisy,运算符 new 和 delete 均被重载。 函数 unexpected_rethrow 可抛出所有类的异常,所以 该函数再次抛出与已知类型完全相同的异常。这样 函数 unexpected_rethrow 的作用就是接收任何未加说 明的异常对象,并作为已知异常对象再次抛出。通 过这种方法,该函数可作为滤波器用以跟踪意外异 常的出现并获取该异常对象的类型。,9.6 构造函数 从上一章的讨论可知,如果构造函数中出现异常, 这将使得类对象不能被完整创建,从而导致类对象需 要撤消时析构函数不能被正常调用,使类对象无法被 撤消。这意味着在编写类的构造函数时,必须十分慎 重地对待是否会有异常发生。另一方面,构造函数进 行存储资源的动态分配是经常发生的,而在存储资源 的动态分配过程中可能有异常发生。如果用于动态分 配存储资源的指针是未加保护的,则析构函数将无法 收回这些存储资源。,例9-5 描述了在对象构造过程中出现异常引起的错误 运行结果产生的文件 nudep.out 中保存了如下信息: useResources() bonk() bonk() bonk() allocating an og inside handler 分析结果不难看出:,类 useResources 的构造函数执行过程中,当通过指针 属性 bp 动态分配 bonk 类型数组空间时, bonk 类型的 构造函数被调用不会抛出异常对象。而当通过指针属 性 op 动态分配 og 类型对象时,则 og:operator new 的 执行中会抛出一个异常对象。这就使得程序在异常处 理器中被意外地结束,而 useResources 的析构函数未 能得到调用。这是因为异常发生时,useResources 构造 函数的全部工作没未完成,使得 useResources 对象所 有的内存空间,包括已经被完整创建的 bonk 类型的数 组空间都不能被清除。,内存动态分配操作的对象化: 为了防止上述情况的发生,应避免对象通过本身的 构造函数将 “不完整” 的资源分配到对象中。所谓对象 化方法是将每个分配操作变成了 “原子型” 的,像一个 类对象,只要是成功分配资源的 “原子型” 对象都能被 正确地清除。模板是实现内存动态分配操作对象化的 一种好方法。 例9-6 展示了对例9-5 进行内存动态分配操作对象化 修改所产生的结果。 运行结果产生的文件 wrapped.out 中保存了如下信息:,bonk() bonk() bonk() pwrap constructor allocating an og bonk() bonk() bonk() pwrap destructor inside handler,分析结果: 不同点是使用类模板 pwrap 封装内存资源的动态分 配操作,并使用 pwrap 实例定义 useResources 的对象 成员 Bonk 和 Og,替代原来的 bonk 类型指针 bp 和 og 类型指针 op。在 useResources 对象的构造过程中,模 板 pwrap 的构造函数被调用,如果这些模板构造函数 所进行的内存资源的动态分配操作完成之后发生了异 常,已分配内存资源就能够被模板析构函数清除。因 此,对象成员 Og 创建时,存储空间分配操作所发生的 异常不会影响为 Bonk 对象数组动态分配的内存空间被 清除,没有内存泄漏。,9.7 异常对象的匹配 当一个异常对象抛出时,异常处理器会根据被抛出 异常对象的类型顺序匹配 “最近” 的异常处理分支。 异常对象的匹配并不要求被抛出的异常对象的类型 和异常处理分支的参数类型完全一致。如果一个被抛 出的异常对象的类型是某个类的派生类,则在异常处 理器中允许用一个使用基类为参数的异常处理分支与 其匹配。但需要注意的是:, 若处理器的参数是基类对象,而不是基类对象的引 用或指针,派生类异常对象在与异常处理器匹配的 过程中将会被 “切片”,被 “切片” 的异常对象虽然不 会受到破坏,但会丢失所有派生类型的新增特征。 系统预定义类型的异常对象在匹配过程中,可以发 生类型的隐含转换,而自定义类型的异常对象在匹 配过程中,不会发生类型的隐含转换。 例9-7 描述了在自定义类型的异常对象匹配过程中不 会自动执行类型的转换。,分析结果: 尽管我们已经通过类 except2 的构造函数提供了将类 except1 对象隐含转换为 except2 对象的功能,但是当 except1 对象作为异常对象被抛出后,在与异常处理器 的第一个异常处理分支匹配过程时,不会被隐含转换 为 except2 对象。 例9-8 描述了在异常处理器中如何使用基类参数捕获 派生类的异常对象。,分析结果: 第一个异常处理分支总能匹配一个 trouble 对象或从 trouble 派生的类对象; 由于第一个异常处理分支捕获了能与第二和第三个 异常处理分支的所有异常对象,导致第二和第三个 异常处理分支永远不会被调用。 异常处理器中的异常对象匹配顺序应该先匹配派生 类异常,而把基类异常的匹配放在最后更有意义; 由于派生类 small 和 big 的对象比基类 trouble 的对象 大,因此在第一个异常处理分支中它们将被 “切片” 以适应异常对象匹配的需要。避免这种信息的剪裁 应该使用基类的引用或指针作为匹配参数。,9.8 标准异常 在 C+ 标准库中提供了一批标准异常类,为用户在 编程中直接使用和作为派生异常类的基类。下面的三 张表描述了这些标准异常类:,其中异常基类 exception 的定义如下: class _CRTIMP exception public: exception(); exception(const _exString 它的派生类,以 logic_error 为例,其定义如下:,class _CRTIMP logic_error : public exception public: explicit logic_error(const string,9.9 含有异常的程序设计 在本章中将论述使用异常处理机制和方法进行程序 设计的一些常用原则。 9.9.1 何时避免使用异常处理 异常处理并不是解决所有运行错误的的最佳方法。 下面是各种不适合使用异常处理的情况: 1 异步事件 标准 C 的信号系统 signal 以及其他类似的控制系统 产生的异步事件。 异步事件发生在程序控制的范围以外,它必须有 完全独立的代码来处理,而不是程序流的一部 分,它的发生是程序所不能预计的。, 异常的发生和异常的处理处于相同的运行环境 中,即异常被限制在一定范围内。 在一些定义明确的程序点上,一个异常可以以基 于中断的方式抛出。 2 普通错误情况 如果一个错误发生时有足够的信息去处理它,这个 错误就应引起一个异常。此时应该关心当前运行环 境,而不应该创建一个描述错误的异常对象,并将 它抛出当前的运行环境。例如,不应当为机器层的 事件(如 “除零溢出”)抛出异常,这些异常可由其 他机制(如操作系统或硬件)去处理。,3 流控制 虽然异常和处理看上去有点像一个交替返回机制, 也有点像一个 switch 语句段,但我们不能用这种机 制去控制程序流,而改变了使用该机制的初衷。 4 不强迫使用异常 一些程序相当简单,例如一些应用程序可能仅仅需 要获取输入和执行一些加工。如果在这类程序中试 图分配存储或使用文件操作等,则可以使用 assert() 显示出错信息,使用 abort() 终止程序。而刻意使用 异常处理方法进行异常对象的创建、抛出、捕获来 实现系统资源修复,则是不明智的。 从根本上说,假如不需要使用异常,就不要使用。,9.9.2 异常处理的典型使用 使用异常处理的优点主要表现在: 便于将问题固定下来和重新测试调用这个导致异常 的函数。 便于修正由异常对象指示的错误,而后继续运行。 便于计算一些选择结果用于代替函数假定产生的结 果。 便于在当前运行环境中尽其所能解决运行错误,并 且便于将不能解决的错误抛出的异常对象抛向更高 一层的运行环境。, 便于根据不能解决的错误抛出的异常对象,创建一 个新的相关的异常对象,并抛向更高一层的运行环 境。 便于终止程序运行。 便于包装使用普通错误处理函数(尤其是 C 的库函 数),以便产生异常处理替代。 便于简化出错处理。 便于更安全地使用库和程序。,在程序设计中使用异常处理机制应该注意: 1 在函数声明中使用异常规格说明 函数原型声明中的异常规格说明是用户如何使用 异常处理机制编写函数调用,处理函数可能发生 的运行错误的程序代码和编译器编译这些代码的 依据。 当函数运行期间,由于不同的原因抛出了函数的 异常规格说明没有声明的异常类型对象,则会导 致对 unexpected() 的调用。因此,在使用异常规格 说明函数可能抛出的异常对象类型的同时,一般 也应定义用户定制的 unexpected() 函数执行代码。,2 基于标准异常类 C+ 的标准异常类应该成为用户在编写函数的异 常规格说明时的首选异常类型。 假若 C+ 的标准异常类不能满足用户编程需要, 则应该尽量从某个已存在的标准异常类派生定义 用户自己的异常形成。特别是使用 exception 作为 用户派生异常类的基类,重新定义该类的虚成员 函数 what() ,这会使用户受益匪浅。,3 套装我们自己的异常 如果为我们的特定类创建异常,在类中套装异常类 是一个好主意,这些异常类仅为我们的特定类使用。还可以防止命名域的混乱。 4 使用异常层次 异常层次为不同类型的重要错误分类提供了一个有 价值的方法,这些错误可能会与我们的类或库冲 突。异常层次可为用户提供有帮助的信息,帮助用 户组织自己的代码,选择是忽略所有异常特定类型 还是正确地捕获基类类型。异常层次使得任何异常 可通过对相同基类的继承而追加,以基类为参数的 异常处理器将捕获新的异常。C+ 的标准异常类是 一个异常层次的优秀范例。,5 多重继承 当需要把一个指向对象的指针向上映射到两个不同 的基类,实现两个基类的多态行为时,使用多重继 承将是十分有效的。多重继承对于异常层次也是非常有用的,因为以多重继承异常的任一个基类为参 数的异常处理器都可以处理多重继承异常。 6 用异常 “引用” 而非异常 “值” 去捕获 如果抛出一个派生类对象而且该对象被基类的异常 处理器通过 “值” 捕获到,对象会被 “切片”,也就是 说,随着向基类对象的传递,派生类元素会被依次 割下,直到传递完成。,例9-9 展示了使用 “引用” 将一个异常的派生类对象传 递给基类处理器时不被 “切片” 的实例。 当派生类 derived 对象通过 “值” 被捕获时,被 “切 片” 成基类 base 对象,表现出 base 对象的行为。 当派生类 derived 对象的 “引用” 被捕获时,对象 不会被 “切片”,表现出派生类的真实情况。 虽然也可以抛出和捕获指针,但这样做会引入更 多的耦合 抛出和捕获器必须为怎样分配和清 除异常对象达成一致,这将是一个问题,因为新 的异常又可能会由于堆的耗尽而产生。,7 在构造函数中抛出异常 由于构造函数没有返回值,如果没有异常机制,只能按以下两种选择报告在构造期间的错误: 设置一个非局部的标志并希望用户检查它。 希望用户检查对象是否被完全创建。 这是一个严重的问题,因为在 C+ 程序中,对象构 造失败后继续执行注定是灾难。所以构造函数成为 抛出异常最重要的用途之一。使用异常机制是处理 构造函数错误的安全有效的方法。然而我们还必须 把注意力集中在对象内部的指针上和构造函数异常 抛出时的清除方法上。,8 不要在析构函数中抛出异常 由于析构函数会在抛出异常时被调用,所以永远不 要在析构函数中抛出一个异常或者通过执行在析构 函数中的动作导致其他异常的抛出。否则就意味着 在已存在的异常到达引起捕获之前又抛出一个新的 异常,这会导致对 terminate() 的调用。换句话说,假 若调用一个析构函数中的任何函数都有可能会抛出 异常,则这些调用应该写在析构函数中的一个测试 块 try 中,而且析构函数必须自己处理所有自身的异 常,即这里的异常都不应逃离析构函数内部。,9.10 开销 使用任何一个新特性必然有所开销。异常被抛出需 要开销相当的运行时间,这就是不要把异常处理用于 程序流控制的一部分原因。 相对于程序的正常执行,异常是偶而发生的。因此 设计异常处理的重要目标之一是:当异常没有发生 时,异常处理代码应不影响运行速度。换句话说,只 要不抛出异常,代
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年房地产项目建筑抗震顾问服务合同范本
- 2025版外墙清洗与外墙涂料保护服务协议
- 2025版膨润土矿产资源承包合同模板
- 2025年度旅游服务管理系统购买与升级合同
- 2025年餐厅装饰装修工程品质保证合同
- 2025保定高端住宅托管出租合作协议
- 2025版施工环保责任协议模板及下载
- 2025版企业劳动合同中保密协议与竞业限制规定
- 2025年度塔吊及人货电梯施工劳务分包项目合作协议
- 2025年度智能机器人项目合同授权委托管理制度
- 人教版(2024)七年级上册生物全册教学设计
- YYT 0660-2008 外科植入物用聚醚醚酮(PEEK)聚合物的标准规范
- 中国石油天然气集团公司专业技术职务任职资格评审工作管理规定
- 卡牌版权合同
- 异常工况安全处置管理制度(根据导则编写)
- DL-T5588-2021电力系统视频监控系统设计规程
- DL-T5366-2014发电厂汽水管道应力计算技术规程
- 石材厂设备保养操作手册
- 金融理财基础知识
- 送别混声合唱简谱
- 全国食品安全风险监测参考值 2024年版
评论
0/150
提交评论