Team LiB
Previous Section Next Section

6.13. try Blocks and Exception Handling

6.13. try 块和异常处理

Handling errors and other anomalous behavior in programs can be one of the most difficult parts of designing any system. Long-lived, interactive systems such as communication switches and routers can devote as much as 90 percent of their code to error detection and error handling. With the proliferation of Web-based applications that run indefinitely, attention to error handling is becoming more important to more and more programmers.

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

Exceptions are run-time anomalies, such as running out of memory or encountering unexpected input. Exceptions exist outside the normal functioning of the program and require immediate handling by the program.

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

In well-designed systems, exceptions represent a subset of the program's error handling. Exceptions are most useful when the code that detects a problem cannot handle it. In such cases, the part of the program that detects the problem needs a way to transfer control to the part of the program that can handle the problem. The error-detecting part also needs to be able to indicate what kind of problem occurred and may want to provide additional information.

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

Exceptions support this kind of communication between the error-detecting and error-handling parts of a program. In C++ exception handling involves:

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

  1. throw expressions, which the error-detecting part uses to indicate that it encountered an error that it cannot handle. We say that a throw raises an exceptional condition.

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

  2. try blocks, which the error-handling part uses to deal with an exception. A try block starts with keyword try and ends with one or more catch clauses. Exceptions thrown from code executed inside a try block are usually handled by one of the catch clauses. Because they "handle" the exception, catch clauses are known as handlers.

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

  3. A set of exception classes defined by the library, which are used to pass the information about an error between a throw and an associated catch.

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

In the remainder of this section we'll introduce these three components of exception handling. We'll have more to say about exceptions in Section 17.1 (p. 688).

在本节接下来的部分将要介绍这三种异常处理的构成。而第 17.1 节将会进一步了解异常的相关内容。

6.13.1. A throw Expression

6.13.1 throw 表达式

An exception is thrown using a throw expression, which consists of the keyword throw followed by an expression. A throw expression is usually followed by a semicolon, making it into an expression statement. The type of the expression determines what kind of exception is thrown.

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

As a simple example, recall the program on page 24 that added two objects of type Sales_item. That program checked whether the records it read referred to the same book. If not, it printed a message and exited.

回顾第 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"
                   << std::endl;
         return -1; // indicate failure
     }

In a less simple program that used Sales_items, the part that adds the objects might be separated from the part that manages the interaction with a user. In this case, we might rewrite the test to throw an exception instead:

在使用 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;

In this code we check whether the ISBNs differ. If so, we discontinue execution and transfer control to a handler that will know how to handle this error.

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

A throw takes an expression. In this case, that expression is an object of type runtime_error. The runtime_error type is one of the standard library exception types and is defined in the stdexcept header. We'll have more to say about these types shortly. We create a runtime_error by giving it a string, which provides additional information about the kind of problem that occurred.

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

6.13.2. The try Block

6.13.2. try

The general form of a try block is

try 块的通用语法形式是:

     try {
         program-statements
     } catch (exception-specifier) {
         handler-statements
     } catch (exception-specifier) {
         handler-statements
     } //...

A try block begins with the keyword try followed by a block enclosed in braces. Following the try block is a list of one or more catch clauses. A catch clause consists of three parts: the keyword catch, the declaration of a single type or single object within parentheses (referred to as an exception specifier), and a block, which as usual must be enclosed in curly braces. If the catch clause is selected to handle an exception, the associated block is executed. Once the catch clause finishes, execution continues with the statement immediately following the last catch clause.

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

The program-statements inside the try constitute the normal logic of the program. They can contain any C++ statement, including declarations. Like any block, a try block introduces a local scope, and variables declared within a try block cannot be referred to outside the try, including within the catch clauses.

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

Writing a Handler
编写处理代码

In the preceeding example we used a throw to avoid adding two Sales_items that represented different books. We imagined that the part of the program that added to Sales_items was separate from the part that communicated with the user. The part that interacts with the user might contain code something like the following to handle the exception that was thrown:

在前面的例子中,使用了 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
         }
     }

Following the try keyword is a block. That block would invoke the part of the program that processes Sales_item objects. That part might throw an exception of type runtime_error.

关键字 try 后面是一个块语句。这个块语句调用处理 Sales_item 对象的程序部分。这部分也可能会抛出 runtime_error 类型的异常。

This try block has a single catch clause, which handles exceptions of type runtime_error. The statements in the block following the catch define the actions that will be executed if code inside the try block throws a runtime_error. Our catch handles the error by printing a message and asking the user to indicate whether to continue. If the user enters an 'n', then we break out of the while. Otherwise the loop continues by reading two new Sales_items.

上述 try 块提供单个 catch 子句,用来处理 runtime_error 类型的异常。在执行 try 块代码的过程中,如果在 try 块中的代码抛出 runtime_error 类型的异常,则处理这类异常的动作在 catch 后面的块语句中定义。本例中,catch 输出信息并且询问用户是否继续进行异常处理。如果用户输入'n',则结束 while;否则继续循环,读入两个新的 Sales_items 对象。

The prompt to the user prints the return from err.what(). We know that err has type runtime_error, so we can infer that what is a member function (Section 1.5.2, p. 24) of the runtime_error class. Each of the library exception classes defines a member function named what. This function takes no arguments and returns a C-style character string. In the case of runtime_error, the C-style string that what returns is a copy of the string that was used to initialize the runtime_error. If the code described in the previous section threw an exception, then the output printed by this catch would be

通过输出 err.what() 的返回值提示用户。大家都知道 err 返回 runtime_error 类型的值,因此可以推断出 whatruntime_error 类的一个成员函数(1.5.2 节)。每一个标准库异常类都定义了名为 what 的成员函数。这个函数不需要参数,返回 C 风格字符串。在出现 runtime_error 的情况下,what 返回的 C 风格字符串,是用于初始化 runtime_errorstring 对象的副本。如果在前面章节描述的代码抛出异常,那么执行这个 catch 将输出。

     Data must refer to same ISBN
     Try Again? Enter y or n

functions are exited during the search for a handler
函数在寻找处理代码的过程中退出

in complex systems the execution path of a program may pass through multiple try blocks before encountering code that actually throws an exception. for example, a try block might call a function that contains a try, that calls another function with its own try, and so on.

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

the search for a handler reverses the call chain. when an exception is thrown, the function that threw the exception is searched first. if no matching catch is found, the function terminates, and the function that called the one that threw is searched for a matching catch. if no handler is found, then that function also exits and the function that called it is searched; and so on back up the execution path until a catch of an appropriate type is found.

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

If no catch clause capable of handling the exception exists, program execution is transferred to a library function named terminate, which is defined in the exception header. The behavior of that function is system dependent, but it usually aborts the program.

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

Exceptions that occur in programs that define no try blocks are handled in the same manner: After all, if there are no try blocks, there can be no handlers for any exception that might be thrown. If an exception occurs, then terminate is called and the program (ordinarily) is aborted.

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

Exercises Section 6.13.2

Exercise 6.23:

The bitset operation to_ulong tHRows an overflow_error exception if the bitset is larger than the size of an unsigned long. Write a program that generates this exception.

bitset 类提供 to_ulong 操作,如果 bitset 提供的位数大于 unsigned long 的长度时,抛出一个 overflow_error 异常。编写产生这种异常的程序。

Exercise 6.24:

Revise your program to catch this exception and print a message.

修改上述的程序,使它能捕获这种异常并输出提示信息。


6.13.3. Standard Exceptions

6.13.3. 标准异常

The C++ library defines a set of classes that it uses to report problems encountered in the functions in the standard library. These standard exception classes are also intended to be used in the programs we write. Library exception classes are defined in four headers:

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

  1. The exception header defines the most general kind of exception class named exception. It communicates only that an exception occurs but provides no additional information.

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

  2. The stdexcept header defines several general purpose exception classes. These types are listed in Table 6.1 on the following page.

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

    Table 6.1. Standard Exception Classes Defined in <stdexcept>
    表 6.1 在 <stdexcept> 头文件中定义的标准异常类

    exception

    The most general kind of problem.

    最常见的问题。

    runtime_error

    Problem that can be detected only at run time.

    运行时错误:仅在运行时才能检测到问题

    range_error

    Run-time error: result generated outside the range of values that are meaningful.

    运行时错误:生成的结果超出了有意义的值域范围

    overflow_error

    Run-time error: computation that overflowed.

    运行时错误:计算上溢

    underflow_error

    Run-time error: computation that underflowed.

    运行时错误:计算下溢

    logic_error

    Problem that could be detected before run time.

    逻辑错误:可在运行前检测到问题

    domain_error

    Logic error: argument for which no result exists.

    逻辑错误:参数的结果值不存在

    invalid_argument

    Logic error: inappropriate argument.

    逻辑错误:不合适的参数

    length_error

    Logic error: attempt to create an object larger than the maximum size for that type.

    逻辑错误:试图生成一个超出该类型最大长度的对象

    out_of_range

    Logic error: used a value outside the valid range.

    逻辑错误:使用一个超出有效范围的值


  3. The new header defines the bad_alloc exception type, which is the exception thrown by new (Section 5.11, p. 174) if it cannot allocate memory.

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

  4. The type_info header defines the bad_cast exception type, which we will discuss in Section 18.2 (p. 772).

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

Standard Library Exception Classes
标准库异常类

The library exception classes have only a few operations. We can create, copy, and assign objects of any of the exception types. The exception, bad_alloc, and bad_cast types define only a default constructor (Section 2.3.4, p. 50); it is not possible to provide an initializer for objects of these types. The other exception types define only a single constructor that takes a string initializer. When we define any of these other exception types, we must supply a string argument. That string initializer is used to provide additional information about the error that occurred.

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

The exception types define only a single operation named what. That function takes no arguments and returns a const char*. The pointer it returns points to a C-style character string (Section 4.3, p. 130). The purpose of this C-style character string is to provide some sort of textual description of the exception thrown.

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

The contents of the C-style character array to which what returns a pointer depends on the type of the exception object. For the types that take a string initializer, the what function returns that string as a C-style character array. For the other types, the value returned varies by compiler.

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

Team LiB
Previous Section Next Section