`

异常处理

阅读更多

try 块和异常处理

在设计各种软件系统的过程中,处理程序中的错误和其他反常行为是困难的部分之一。像通信交换机和路由器这类长期运行的交互式系统必须将 90% 的程序代码用于实现错误检测和错误处理。随着基于 Web 的应用程序在运行时不确定性的增多,越来越多的程序员更加注重错误的处理。

异常就是运行时出现的不正常,例如运行时耗尽了内存或遇到意外的非法输入。异常存在于程序的正常功能之外,并要求程序立即处理。

在设计良好的系统中,异常是程序错误处理的一部分。当程序代码检查到无法处理的问题时,异常处理就特别有用。在这些情况下,检测出问题的那部分程序需要一种方法把控制权转到可以处理这个问题的那部分程序。错误检测程序还必须指出具体出现了什么问题,并且可能需要提供一些附加信息。

异常机制提供程序中错误检测与错误处理部分之间的通信。C++ 的异常处理中包括:

1. throw 表达式,错误检测部分使用这种表达式来说明遇到了不可处理的错误。可以说,throw 引发了异常条件。

2. try 块,错误处理部分使用它来处理异常。try 语句块以 try 关键字开始,并以一个或多个 catch 子句结束。在 try 块中执行的代码所抛出(throw )的异常,通常会被其中一个 catch 子句处理。由于它们“处理”异常,catch 子句也称为处理代码。

3. 由标准库定义的一组 异常类,用来在 throw 和相应的 catch 之间传递有关的错误信息。

在本节接下来的部分将要介绍这三种异常处理的构成。

throw 表达式

系统通过 throw 表达式抛出异常。throw 表达式由关键字 throw 以及尾随的表达式组成,通常以分号结束,这样它就成为了表达式语句。throw 表达式的类型决定了所抛出异常的类型。

回顾第 1.5.2 节将两个 Sales_item 类型对象相加的程序,就是一个简单的例子。该程序检查读入的记录是否来自同一本书。如果不是,就输出一条信息然后退出程序。

Sales_item item1, item2;

std::cin >> item1 >> item2;

// first check that item1 and item2 represent the same book

if (item1.same_isbn(item2)) {

std::cout << item1 + item2 << std::endl;

return 0; // indicate success

} else {

std::cerr << "Data must refer to same ISBN"

295

<< std::endl;

return -1; // indicate failure

}

在使用 Sales_items 的更简单的程序中,把将对象相加的部分和负责跟用户交互的部分分开。在这个例子中,用 throw 抛出异常来改写检测代码:

// first check that data is for the same item

if (!item1.same_isbn(item2))

throw runtime_error("Data must refer to same ISBN");

// ok, if we're still here the ISBNs are the same

std::cout << item1 + item2 << std::endl;

这段代码检查 ISBN 对象是否不相同。如果不同的话,停止程序的执行,并将控制转移给处理这种错误的处理代码。

throw 语句使用了一个表达式。在本例中,该表达式是 runtime_error 类型的对象。runtime_error 类型是标准库异常类中的一种,在 stdexcept 头文件中定义。在后续章节中很快就会更详细地介绍这些类型。我们通过传递 string 对象来创建 runtime_error 对象,这样就可以提供更多关于所出现问题的相关信息。

try

try 块的通用语法形式是:

try {

program-statements

} catch (exception-specifier) {

handler-statements

} catch (exception-specifier) {

handler-statements

} //...

try 块以关键字 try 开始,后面是用花括号起来的语句序列块。try 块后面是一个或多个 catch 子句。每个 catch 子句包括三部分:关键字 catch ,圆括号内单个类型或者单个对象的声明——称为异常说明符,以及通常用花括号括起来的语句块。如果选择了一个 catch 子句来处理异常,则执行相关的块语句。一旦 catch 子句执行结束,程序流程立即继续执行紧随着最后一个 catch 子句的语句。

try 语句内的 program-statements 形成程序的正常逻辑。这里面可以包含任意 C++ 语句,包括变量声明。与其他块语句一样,try 块引入局部作用域,在 try 块中声明的变量,包括 catch 子句声明的变量,不能在 try 外面引用。

编写处理代码

在前面的例子中,使用了 throw 来避免将两个表示不同书的 Sales_items 对象相加。想象一下将 Sales_items 对象相加的那部分程序与负责与用户交流的那部分是分开的,则与用户交互的部分也许会包含下面的用于处理所捕获异常的代码:

while (cin >> item1 >> item2) {

try {

// execute code that will add the two Sales_items

// if the addition fails, the code throws a runtime_error

exception

} catch (runtime_error err) {

// remind the user that ISBN must match and prompt for

another pair

cout << err.what()

<< "\nTry Again? Enter y or n" << endl;

char c;

cin >> c;

if (cin && c == 'n')

break; // break out of the while loop

}

}

关键字 try 后面是一个块语句。这个块语句调用处理 Sales_item 对象的程序部分。这部分也可能会抛出 runtime_error 类型的异常。上述 try 块提供单个 catch 子句,用来处理 runtime_error 类型的异常。在执行 try 块代码的过程中,如果在 try 块中的代码抛出 runtime_error 类型的异常,则处理这类异常的动作在 catch 后面的块语句中定义。本例中,catch 输出信息并且询问用户是否继续进行异常处理。如果用户输入'n' ,则结束while ;否则继续循环,读入两个新的 Sales_items 对象。通过输出 err.what() 的返回值提示用户。大家都知道 err 返回runtime_error 类型的值,因此可以推断出 what runtime_error 类的一个成员函数(1.5.2 节)。每一个标准库异常类都定义了名为 what 的成员函数。这个函数不需要参数,返回 C 风格字符串。在出现 runtime_error 的情况下,what 返回的 C 风格字符串,是用于初始化runtime_error string 对象的副本。如果在前面章节描述的代码抛出异常,那么执行这个 catch 将输出。

Data must refer to same ISBN

Try Again? Enter y or n

函数在寻找处理代码的过程中退出

在复杂的系统中,程序的执行路径也许在遇到抛出异常的代码之前,就已经经过了多个 try 块。例如,一个 try 块可能调用了包含另一 try 块的函数,它的 try 块又调用了含有 try 块的另一个函数,如此类推。

寻找处理代码的过程与函数调用链刚好相反。抛出一个异常时,首先要搜索的是抛出异常的函数。如果没有找到匹配的 catch ,则终止这个函数的执行,并在调用这个函数的函数中寻找相配的 catch 。如果仍然找到相应的处理代码,该函数同样要终止,搜索调用它的函数。如此类推,继续按执行路径回退,直到找到适当类型的 catch 为止。

如果不存在处理该异常的 catch 子句,程序的运行就要跳转到名为terminate 的标准库函数,该函数在 exception 头文件中定义。这个标准库函数的行为依赖于系统,通常情况下,它的执行将导致程序非正常退出。

在程序中出现的异常,如果没有经 try 块定义,则都以相同的方式来处理:毕竟,如果没有任何 try 块,也就没有捕获异常的处理代码(catch 子句)。此时,如果发生了异常,系统将自动调用 terminate 终止程序的执行。

标准异常

C++ 标准库定义了一组类,用于报告在标准库中的函数遇到的问题。程序员可在自己编写的程序中使用这些标准异常类。标准库异常类定义在四个头文件中:

1. exception 头文件定义了最常见的异常类,它的类名是 exception 。这个类只通知异常的产生,但不会提供更多的信息。

2. stdexcept 头文件定义了几种常见的异常类,这些类型在表 6.1 中列出。

6.1 <stdexcept> 头文件中定义的标准异常类

new 头文件定义了 bad_alloc 异常类型,提供因无法分配内在而由 new (第 5.11 节)抛出的异常。

3. type_info 头文件定义了 bad_cast 异常类型,这种类型将第 18.2 节讨论。

标准库异常类

标准库异常类只提供很少的操作,包括创建、复制异常类型对象以及异常类型对象的赋值。 exceptionbad_alloc 以及 bad_cast 类型只定义了默认构造函数(第 2.3.4 节),无法在创建这些类型的对象时为它们提供初值。其他的异常类型则只定义了一个使用 string 初始化式的构造函数。当需要定义这些异常类型的对象时,必须提供一想 string 参数。string 初始化式用于为所发生的错误提供更多的信息。

异常类型只定义了一个名为 what 的操作。这个函数不需要任何参数,并且返回 const char* 类型值。它返回的指针指向一个 C 风格字符串(第 4.3 节)。使用 C 风格字符串的目的是为所抛出的异常提出更详细的文字描述。

what 函数所返回的指针指向 C 风格字符数组的内容,这个数组的内容依赖于异常对象的类型。对于接受 string 初始化式的异常类型,what 函数将返回该 string 作为 C 风格字符数组。对于其他异常类型,返回的值则根据编译器的变化而不同。

使用预处理器进行调试

2.9.2 节介绍了如何使用预处理变量来避免重复包含头文件。C++ 程序员有时也会使用类似的技术有条件地执行用于调试的代码。这种想法是:程序所包含的调试代码仅在开发过程中执行。当应用程序已经完成,并且准备提交时,就会将调试代码关闭。可使用 NDEBUG 预处理变量实现有条件的调试代码:

int main()

{

#ifndef NDEBUG

cerr << "starting main" << endl;

#endif

// ...

如果 NDEBUG 未定义,那么程序就会将信息写到 cerr 中。如果 NDEBUG 已经定义了,那么程序执行时将会跳过 #ifndef #endif 之间的代码。

默认情况下,NDEBUG 未定义,这也就意味着必须执行 #ifndef #endif 之间的代码。在开发程序的过程中,只要保持 NDEBUG 未定义就会执行其中的调试语句。开发完成后,要将程序交付给客户时,可通过定义 NDEBUG 预处理变量,(有效地)删除这些调试语句。大多数的编译器都提供定义 NDEBUG 命令行选项:

$ CC -DNDEBUG main.C

这样的命令行行将于在 main.c 的开头提供 #define NDEBUG 预处理命令。

预处理器还定义了其余四种在调试时非常有用的常量:

__FILE__ 文件名

__LINE__ 当前行号

__TIME__ 文件被编译的时间

__DATE__ 文件被编译的日期

可使用这些常量在错误消息中提供更多的信息:

if (word.size() < threshold)

cerr << "Error: " << _ _FILE_ _

<< " : line " << _ _LINE_ _ << endl

<< " Compiled on " << _ _DATE_ _

<< " at " << _ _TIME_ _ << endl

<< " Word read was " << word

<< ": Length too short" << endl;

如果给这个程序提供一个比 threshold 短的 string 对象,则会产生下面

的错误信息:

Error: wdebug.cc : line 21

Compiled on Jan 12 2005 at 19:44:40

Word read was "foo": Length too short

另一个常见的调试技术是使用 NDEBUG 预处理变量以及 assert 预处理宏。assert 宏是在 cassert 头文件中定义的,所有使用 assert 的文件都必须包含这个头文件。预处理宏有点像函数调用。assert 宏需要一个表达式作为它的条件:

assert(expr)

只要 NDEBUG 未定义,assert 宏就求解条件表达式 expr ,如果结果为falseassert 输出信息并且终止程序的执行。如果该表达式有一个非零(例如,true )值,则 assert 不做任何操作。与异常不同(异常用于处理程序执行时预期要发生的错误),程序员使用assert 来测试“不可能发生”的条件。例如,对于处理输入文本的程序,可以预测全部给出的单词都比指定的阈值长。那么程序可以包含这样一个语句:

assert(word.size() > threshold);

在测试过程中,assert 等效于检验数据是否总是具有预期的大小。一旦开发和测试工作完成,程序就已经建立好,并且定义了 NDEBUG 。在成品代码中,assert 语句不做任何工作,因此也没有任何运行时代价。当然,也不会引起任何运行时检查。assert 仅用于检查确实不可能的条件,这只对程序的调试有帮助,但不能用来代替运行时的逻辑检查,也不能代替对程序可能产生的错误的检测。

分享到:
评论

相关推荐

    Spring Cloud Gateway的全局异常处理

    Spring Cloud Gateway的全局异常处理 Spring Cloud Gateway中的全局异常处理不能直接用@ControllerAdvice来处理,通过跟踪异常信息的抛出,找到对应的源码,自定义一些处理逻辑来符合业务的需求。 网关都是给接口做...

    C#异常处理总结及简单实例

    C#异常处理总结及简单实例 一、异常处理的理解? 异常处理是指程序在运行过程中,发生错误会导致程序退出,这种错误,就叫做异常。 因此处理这种错误,就称为异常处理。 二、异常处理如何操作? C# 异常处理时建立在...

    异常处理.ppt异常处理.ppt异常处理.ppt异常处理.ppt异常处理.ppt

    异常处理.ppt异常处理.ppt异常处理.ppt异常处理.ppt异常处理.ppt异常处理.ppt

    两数计算+异常处理

    课程作业,实现两数计算及其异常处理,异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。 Java中的异常可以是函数...

    精讲RestTemplate自定义请求失败异常处理.docx

    在开始进行自定义的异常处理逻辑之前,我们有必要看一下异常处理的默认实现。也就是:为什么会产生上面小节提到的现象? ResponseErrorHandler是RestTemplate请求结果的异常处理器接口 o接口的第一个方法hasError...

    异常处理 异常处理 异常处理

    关于异常处理的word文档 关于异常处理的word文档 关于异常处理的word文档

    Springboot全局异常处理demo.zip

    Springboot全局异常处理demo 项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统的...

    MySQL定义异常和异常处理详解

    主要为大家详细介绍了MySQL定义异常和异常处理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

    C和C++中的异常处理

    2. Microsoft 对异常处理方法的扩展 3. 标准 C++异常处理的基本语法和语义 4. 实例剖析 EH 5. C++的 new 和 delete 操作时的异常处理 6. Microsoft 对于的实现版本中的异常处理 7. 部分构造及 placement delete 8. ...

    C++ 异常处理 C++ 异常处理

    C++ 异常处理 C++ 异常处理C++ 异常处理C++ 异常处理C++ 异常处理C++ 异常处理C++ 异常处理C++ 异常处理

    java异常处理java异常处理

    java异常处理java异常处理java异常处理java异常处理java异常处理java异常处理java异常处理java异常处理java异常处理java异常处理java异常处理java异常处理java异常处理

    c/vc++/MFC异常处理/结构化异常处理 浅析

    c/vc++/结构化异常处理 浅析 C语言异常处理 C++语言异常处理 异常处理函数 MFC异常处理 结构化异常处理

    spingmvc+mybatis+统一异常处理机制

    统一异常处理会区分前端是否ajax请求,自动返回json数据格式,要求开发人员在处理ajax请求时统一封装成一个对象返回,以符合代码统一规范。 此工程在idea环境编写,导入请自己新建工程手工复制代码导入。

    ADS异常处理.pptADS异常处理.pptADS异常处理.ppt

    ADS异常处理.pptADS异常处理.pptADS异常处理.ppt

    ARM异常处理机制ARM异常处理机制

    ARM异常处理机制ARM异常处理机制ARM异常处理机制ARM异常处理机制ARM异常处理机制ARM异常处理机制ARM异常处理机制ARM异常处理机制ARM异常处理机制

    详解SpringCloud Finchley Gateway 统一异常处理

    主要介绍了详解SpringCloud Finchley Gateway 统一异常处理,非常具有实用价值,需要的朋友可以参考下

    工作流系统异常处理实现方法

    中的异常处理问题显得尤为突出[1-3]。传统上将异常处理包含于正常流程中的方法不仅不能 有效的处理各类异常,同时使得整个系统流程复杂化;而完全的人工参与也使得异常处理过 程效率低下,形式极不规范。所以,工作...

    第7章 java异常处理

    第7章 java异常处理

    SQLserver存储过程异常处理.txt

    SQLserver存储过程异常处理

    异常处理机制知识点小总结

    这是自己通过看书对java异常处理的一些总结

Global site tag (gtag.js) - Google Analytics