Team LiB
Previous Section Next Section

17.1. Exception Handling

17.1. 异常处理

Exception handling allows independently developed parts of a program to communicate about and handle problems that arise during execution of the program. One part of the program can detect a problem that that part of the program cannot resolve. The problem-detecting part can pass the problem along to another part that is prepared to handle what went wrong.

使用异常处理,程序中独立开发的各部分能够就程序执行期间出现的问题相互通信,并处理这些问题。程序的一个部分能够检测出本部分无法解决的问题,这个问题检测部分可以将问题传递给准备处理问题的其他部分。

Exceptions let us separate problem detection from problem resolution. The part of the program that detects a problem need not know how to deal with it.

通过异常我们能够将问题的检测和问题的解决分离,这样程序的问题检测部分可以不必了解如何处理问题。



In C++, exception handling relies on the problem-detecting part throwing an object to a handler. The type and contents of that object allow the two parts to communicate about what went wrong.

C++ 的异常处理中,需要由问题检测部分抛出一个对象给处理代码,通过这个对象的类型和内容,两个部分能够就出现了什么错误进行通信。

Section 6.13 (p. 215) introduced the basic concepts and mechanics of using exceptions in C++. In that section, we hypothesized that a more complex bookstore application might use exceptions to communicate about problems. For example, the Sales_item addition operator might throw an exception if the isbn members of its operands didn't match:

第 6.13 节介绍了在 C++ 中使用异常的基本概念和基本机制,在该本节中,假定更复杂的书店应用程序可以通过异常就出现的问题进行通信。例如,如果操作数的 isbn 成员不匹配,Sales_item 的加操作符可以抛出一个异常:


	
     // throws exception if both objects do not refer to the same isbn
     Sales_item
     operator+(const Sales_item& lhs, const Sales_item& rhs)
     {
         if (!lhs.same_isbn(rhs))
             throw runtime_error("Data must refer to same ISBN");
         // ok, if we're still here the ISBNs are the same so it's okay to do the addition
         Sales_item ret(lhs);                   // copy lhs into a local object that we'll
 return
         ret += rhs;                            // add in the contents of rhs
         return ret;                            // return a copy of ret
     }

Those parts of the program that added Sales_item objects would use a try block in order to catch an exception if one occured:

程序中将 Sales_item 对象相加的那些部分可以使用一个 try 块,以便在异常发生时捕获异常:

     // part of the application that interacts with the user
     Sales_item item1, item2, sum;
     while (cin >> item1 >> item2) {       // read two transactions
         try {
             sum = item1 + item2;         // calculate their sum
             // use sum
         } catch (const runtime_error &e) {
           cerr << e.what() << " Try again.\n"
                << endl;
         }
     }

In this section we'll expand our coverage of these basics and cover some additional exception-handling facilities. Effective use of exception handling requires understanding what happens when an exception is thrown, what happens when it is caught, and the meanings of the objects used to communicate what went wrong.

本节将扩展对这些基础知识的讨论并涵盖另外一些异常处理设施。有效使用异常处理需要理解:在抛出异常时会发生什么,在捕获异常时又会发生什么,还有用来传递错误的对象的含义。

17.1.1. Throwing an Exception of Class Type

17.1.1. 抛出类类型的异常

An exception is raised by throwing an object. The type of that object determines which handler will be invoked. The selected handler is the one nearest in the call chain that matches the type of the object.

异常是通过抛出对象而引发的。该对象的类型决定应该激活哪个处理代码。被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那个。

Exceptions are thrown and caught in ways that are similar to how arguments are passed to functions. An exception can be an object of any type that can be passed to a nonreference parameter, meaning that it must be possible to copy objects of that type.

异常以类似于将实参传递给函数的方式抛出和捕获。异常可以是可传给非引用形参的任意类型的对象,这意味着必须能够复制该类型的对象。

Recall that when we pass an argument of array or function type, that argument is automatically converted to an pointer. The same automatic conversion happens for objects that are thrown. As a consequence, there are no exceptions of array or function types. Instead, if we throw an array, the thrown object is converted to a pointer to the first element in the array. Similarly, if we throw a function, the function is converted to a pointer to the function (Section 7.9, p. 276).

回忆一下,传递数组或函数类型实参的时候,该实参自动转换为一个指针。被抛出的对象将发生同样的自动转换,因此,不存在数组或函数类型的异常。相反。相反,如果抛出一个数组,被抛出的对象转换为指向数组首元素的指针,类似地,如果抛出一个函数,函数被转换为指向该函数的指针第 7.9 节

When a throw is executed, the statement(s) following the throw are not executed. Instead, control is transferred from the tHRow to the matching catch. That catch might be local to the same function or might be in a function that directly or indirectly called the function in which the exception occurred. The fact that control passes from one location to another has two important implications:

执行 throw 的时候,不会执行跟在 throw 后面的语句,而是将控制从 throw 转移到匹配的 catch,该 catch 可以是同一函数中局部的 catch,也可以在直接或间接调用发生异常的函数的另一个函数中。控制从一个地方传到另一地方,这有两个重要含义:

  1. Functions along the call chain are prematurely exited. Section 17.1.2 (p. 691) discusses what happens when functions are exited due to an exception.

    沿着调用链的函数提早退出。第 17.1.2 节将讨论函数因异常而退出时会发生什么。

  2. In general, the storage that is local to a block that throws an exception is not around when the exception is handled.

    一般而言,在处理异常的时候,抛出异常的块中的局部存储不存在了。

Because local storage is freed while handling an exception, the object that is thrown is not stored locally. Instead, the throw expression is used to initialize a special object referred to as the exception object. The exception object is managed by the compiler and is guaranteed to reside in space that will be accessible to whatever catch is invoked. This object is created by a throw, and is initialized as a copy of the expression that is thrown. The exception object is passed to the corresponding catch and is destroyed after the exception is completely handled.

因为在处理异常的时候会释放局部存储,所以被抛出的对象就不能再局部存储,而是用 throw 表达式初始化一个称为异常对象的特殊对象。异常对象由编译器管理,而且保证驻留在可能被激活的任意 catch 都可以访问的空间。这个对象由 throw 创建,并被初始化为被抛出的表达式的副本。异常对象将传给对应的 catch,并且在完全处理了异常之后撤销。

The exception object is created by copying the result of the thrown expression; that result must be of a type that can be copied.

异常对象通过复制被抛出表达式的结果创建,该结果必须是可以复制的类型。



Exception Objects and Inheritance
异常对象与继承

In practice, many applications throw expressions whose type comes from an inheritance hierarchy. As we'll see in Section 17.1.7 (p. 697), the standard exceptions (Section 6.13, p. 215) are defined in an inheritance hierarchy. What's important to know at this point is how the form of the tHRow expression interacts with types related by inheritance.

在实践中,许多应用程序所抛出的表达式,基类型都来自某个继承层次。正如第 17.1.7 节将介绍的,标准异常(第 6.13 节)定义在一个继承层次中。目前重要的是,知道 throw 表达式的形式如何与因继承而相关的类型相互影响。

When an exception is thrown, the static, compile-time type of the thrown object determines the type of the exception object.

当抛出一个表达式的时候,被抛出对象的静态编译时类型将决定异常对象的类型。



Ordinarily, the fact that the object is thrown using its static type is not an issue. When we throw an exception, we usually construct the object we are going to throw at the throw point. That object represents what went wrong, so we know the precise exception type.

通常,使用静态类型抛出对象不成问题。当抛出一个异常的时候,通常在抛出点构造将抛出的对象,该对象表示出了什么问题,所以我们知道确切的异常类型。

Exceptions and Pointers
异常与指针

The one case where it matters that a throw expression throws the static type is if we dereference a pointer in a throw. The result of dereferencing a pointer is an object whose type matches the type of the pointer. If the pointer points to a type from an inheritance hierarchy, it is possible that the type of the object to which the pointer points is different from the type of the pointer. Regardless of the object's actual type, the type of the exception object matches the static type of the pointer. If that pointer is a base-class type pointer that points to a derived-type object, then that object is sliced down (Section 15.3.1, p. 577); only the base-class part is thrown.

用抛出表达式抛出静态类型时,比较麻烦的一种情况是,在抛出中对指针解引用。对指针解引用的结果是一个对象,其类型与指针的类型匹配。如果指针指向继承层次中的一种类型,指针所指对象的类型就有可能与指针的类型不同。无论对象的实际类型是什么,异常对象的类型都与指针的静态类型相匹配。如果该指针是一个指向派生类对象的基类类型指针,则那个对象将被分割(第 15.3.1 节),只抛出基类部分。

A problem more serious than slicing the object may arise if we throw the pointer itself. In particular, it is always an error to throw a pointer to a local object for the same reasons as it is an error to return a pointer to a local object (Section 7.3.2, p. 249) from a function. When we throw a pointer, we must be certain that the object to which the pointer points will exist when the handler is entered.

如果抛出指针本身,可能会引发比分割对象更严重的问题。具体而言,抛出指向局部对象的指针总是错误的,其理由与从函数返回指向局部对象的指针是错误的一样(第 7.3.2 节)抛出指针的时候,必须确定进入处理代码时指针所指向的对象存在。

If we throw a pointer to a local object and the handler is in another function, then the object to which the pointer points will no longer exist when the handler is executed. Even if the handler is in the same function, we must be sure that the object to which the pointer points exists at the site of the catch. If the pointer points to an object in a block that is exited before the catch, then that local object will have been destroyed before the catch.

如果抛出指向局部对象的指针,而且处理代码在另一函数中,则执行处理代码时指针所指向的对象将不再存在。即使处理代码在同一函数中,也必须确信指针所指向的对象在 catch 处存在。如果指针指向某个在 catch 之前退出的块中的对象,那么,将在 catch 之前撤销该局部对象。

It is usually a bad idea to throw a pointer: Throwing a pointer requires that the object to which the pointer points exist wherever the corresponding handler resides.

抛出指针通常是个坏主意:抛出指针要求在对应处理代码存在的任意地方存在指针所指向的对象。



Exercises Section 17.1.1

Exercise 17.1:

What is the type of the exception object in the following throws:

下面的 throw 语句中,异常对象的类型是什么?

     (a) range_error r("error");  (b) exception *p = &r;
         throw r;                     throw *p;

Exercise 17.2:

What would happen if the second throw were written as throw p?

如果第二个 throw 语句写成 throw p,会发生什么情况?


17.1.2. Stack Unwinding

17.1.2. 栈展开

When an exception is thrown, execution of the current function is suspended and the search begins for a matching catch clause. The search starts by checking whether the throw itself is located inside a try block. If so, the catch clauses associated with that try are examined to see if one of them matches the thrown object. If a matching catch is found, the exception is handled. If no catch is found, the current function is exitedits memory is freed and local objects are destroyedand the search continues in the calling function.

抛出异常的时候,将暂停当前函数的执行,开始查找匹配的 catch 子句。首先检查 throw 本身是否在 try 块内部,如果是,检查与该 catch 相关的 catch 子句,看是否其中之一与抛出对象相匹配。如果找到匹配的 catch,就处理异常;如果找不到,就退出当前函数(释放当前函数的内在并撤销局部对象),并且继续在调用函数中查找。

If the call to the function that threw is in a try block, then the catch clauses associated with that try are examined. If a matching catch is found, the exception is handled. If no matching catch is found, the calling function is also exited, and the search continues in the function that called this one.

如果对抛出异常的函数的调用是在 try 块中,则检查与该 try 相关的 catch 子句。如果找到匹配的 catch,就处理异常;如果找不到匹配的 catch,调用函数也退出,并且继续在调用这个函数的函数中查找。

This process, known as stack unwinding, continues up the chain of nested function calls until a catch clause for the exception is found. As soon as a catch clause that can handle the exception is found, that catch is entered, and execution continues within this handler. When the catch completes, execution continues at the point immediately after the last catch clause associated with that try block.

这个过程,称之为栈展开(stack unwinding),沿嵌套函数调用链继续向上,直到为异常找到一个 catch 子句。只要找到能够处理异常的 catch 子句,就进入该 catch 子句,并在该处理代码中继续执行。当 catch 结束的时候,在紧接在与该 try 块相关的最后一个 catch 子句之后的点继续执行。

Destructors Are Called for Local Objects
为局部对象调用析构函数

During stack unwinding, the function containing the throw, and possibly other functions in the call chain, are exited prematurely. In general, these functions will have created local objects that ordinarily would be destroyed when the function exited. When a function is exited due to an exception, the compiler guarantees that the local objects are properly destroyed. As each function exits, its local storage is freed. Before releasing the memory, any local object that was created before the exception occurred is destroyed. If the local object is of class type, the destructor for this object is called automatically. As usual, the compiler does no work to destroy an object of built-in type.

栈展开期间,提早退出包含 throw 的函数和调用链中可能的其他函数。一般而言,这些函数已经创建了可以在退出函数时撤销的局部对象。因异常而退出函数时,编译器保证适当地撤销局部对象。每个函数退出的时候,它的局部存储都被释放,在释放内存之前,撤销在异常发生之前创建的所有对象。如果局部对象是类类型的,就自动调用该对象的析构函数。通常,编译器不撤销内置类型的对象。

During stack unwinding, the memory used by local objects is freed and destructors for local objects of class type are run.

栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。



If a block directly allocates a resource, and the exception occurs before that resource is freed, that resource will not be freed during stack unwinding. For example, a block might dynamically allocate memory through a call to new. If the block exits due to an exception, the compiler does not delete the pointer. The allocated memory will not be freed.

如果一个块直接分配资源,而且在释放资源之前发生异常,在栈展开期间将不会释放该资源。例如,一个块可以通过调用 new 动态分配内存,如果该块因异常而退出,编译器不会删除该指针,已分配的内在将不会释放。

Resources allocated by an object of class type generally will be properly freed. Destructors for local objects are run; resources allocated by class-type objects ordinarily are freed by their destructor. Section 17.1.8 (p. 700) describes a programming technique that uses classes to manage resource allocation in the face of exceptions.

由类类型对象分配的资源一般会被适当地释放。运行局部对象的析构函数,由类类型对象分配的资源通常由它们的析构函数释放。第 17.1.8 节说明面对异常使用类管理资源分配的编程技术。

Destructors Should Never throw Exceptions
析构函数应该从不抛出异常

Destructors are often executed during stack unwinding. When destructors are executing, the exception has been raised but not yet handled. It is unclear what should happen if a destructor itself throws a new exception during this process. Should the new exception supersede the earlier exception that has not yet been handled? Should the exception in the destructor be ignored?

栈展开期间会经常执行析构函数。在执行析构函数的时候,已经引发了异常但还没有处理它。如果在这个过程中析构函数本身抛出新的异常,又会发生什么呢?新的异常应该取代仍未处理的早先的异常吗?应该忽略析构函数中的异常吗?

The answer is that while stack unwinding is in progress for an exception, a destructor that throws another exception of its own that it does not also handle, causes the library terminate function is called. Ordinarily, terminate calls abort, forcing an abnormal exit from the entire program.

答案是:在为某个异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常,将会导致调用标准库 terminate 函数。一般而言,terminate 函数将调用 abort 函数,强制从整个程序非正常退出。

Because terminate ends the program, it is usually a very bad idea for a destructor to do anything that might cause an exception. In practice, because destructors free resources, it is unlikely that they throw exceptions. The standard library types all guarantee that their destructors will not raise an exception.

因为 terminate 函数结束程序,所以析构函数做任何可能导致异常的事情通常都是非常糟糕的主意。在实践中,因为析构函数释放资源,所以它不太可能抛出异常。标准库类型都保证它们的析构函数不会引发异常。

Exceptions and Constructors
异常与构造函数

Unlike destructors, it is often the case that something done inside a constructor might throw an exception. If an exception occurs while constructing an object, then the object might be only partially constructed. Some of its members might have been initialized, and others might not have been initialized before the exception occurs. Even if the object is only partially constructed, we are guaranteed that the constructed members will be properly destroyed.

与析构函数不同,构造函数内部所做的事情经常会抛出异常。如果在构造函数对象的时候发生异常,则该对象可能只是部分被构造,它的一些成员可能已经初始化,而另一些成员在异常发生之前还没有初始化。即使对象只是部分被构造了,也要保证将会适当地撤销已构造的成员。

Similarly, an exception might occur when initializing the elements of an array or other container type. Again, we are guaranteed that the constructed elements will be destroyed.

类似地,在初始化数组或其他容器类型的元素的时候,也可能发生异常,同样,也要保证将会适当地撤销已构造的元素。

Uncaught Exceptions Terminate the Program
未捕获的异常终止程序

An exception cannot remain unhandled. An exception is an important enough event that the program cannot continue executing normally. If no matching catch is found, then the program calls the library terminate function.

不能不处理异常。异常是足够重要的、使程序不能继续正常执行的事件。如果找不到匹配的 catch,程序就调用库函数 terminate

17.1.3. Catching an Exception

17.1.3. 捕获异常

The exception specifier in a catch clause looks like a parameter list that contains exactly one parameter. The exception specifier is a type name followed by an optional parameter name.

catch 子句中的异常说明符看起来像只包含一个形参的形参表,异常说明符是在其后跟一个(可选)形参名的类型名。

The type of the specifier determines what kinds of exceptions the handler can catch. The type must be a complete type: It must either be a built-in type or a programmer-defined type that has already been defined. A forward declaration for the type is not sufficient.

说明符的类型决定了处理代码能够捕获的异常种类。类型必须是完全类型,即必须是内置类型或者是已经定义的程序员自定义类型。类型的前向声明不行。

An exception specifier can omit the parameter name when a catch needs to know only the type of the exception in order to handle it. If the handler needs information beyond what type of exception occurred, then its exception specifier will include a parameter name. The catch uses the name to get access to the exception object.

catch 为了处理异常只需要了解异常的类型的时候,异常说明符可以省略形参名;如果处理代码需要已发生异常的类型之外的信息,则异常说明符就包含形参名,catch 使用这个名字访问异常对象。

Finding a Matching Handler
查找匹配的处理代码

During the search for a matching catch, thecatch that is found is not necessarily the one that matches the exception best. Instead, the catch that is selected is the first catch found that can handle the exception. As a consequence, in a list of catch clauses, the most specialized catch must appear first.

在查找匹配的 catch 期间,找到的 catch 不必是与异常最匹配的那个 catch,相反,将选中第一个找到的可以处理该异常的 catch。因此,在 catch 子句列表中,最特殊的 catch 必须最先出现。

The rules for when an exception matches a catch exception specifier are much more restrictive than the rules used for matching arguments with parameter types. Most conversions are not allowedthe types of the exception and the catch specifier must match exactly with only a few possible differences:

异常与 catch 异常说明符匹配的规则比匹配实参和形参类型的规则更严格,大多数转换都不允许——除下面几种可能的区别之外,异常的类型与 catch 说明符的类型必须完全匹配:

  • Conversions from nonconst to const are allowed. That is, a throw of a nonconst object can match a catch specified to take a const reference.

    允许从非 constconst 的转换。也就是说,非 const 对象的 throw 可以与指定接受 const 引用的 catch 匹配。

  • Conversions from derived type to base type are allowed.

    允许从派生类型型到基类类型的转换。

  • An array is converted to a pointer to the type of the array; a function is converted to the appropriate pointer to function type.

    将数组转换为指向数组类型的指针,将函数转换为指向函数类型的适当指针。

No other conversions are allowed when looking for a matching catch. In particular, neither the standard arithmetic conversions nor conversions defined for class types are permitted.

在查找匹配 catch 的时候,不允许其他转换。具体而言,既不允许标准算术转换,也不允许为类类型定义的转换。

Exception Specifiers
异常说明符

When a catch is entered, the catch parameter is initialized from the exception object. As with a function parameter, the exception-specifier type might be a reference. The exception object itself is a copy of the object that was thrown. Whether the exception object is copied again into the catch site depends on the exception-specifier type.

进入 catch 的时候,用异常对象初始化 catch 的形参。像函数形参一样,异常说明符类型可以是引用。异常对象本身是被抛出对象的副本。是否再次将异常对象复制到 catch 位置取决于异常说明符类型。

If the specifier is not a reference, then the exception object is copied into the catch parameter. The catch operates on a local copy of the exception object. Any changes made to the catch parameter are made to the copy, not to the exception object itself. If the specifier is a reference, then like a reference parameter, there is no separate catch object; the catch parameter is just another name for the exception object. Changes made to the catch parameter are made to the exception object.

如果说明符不是引用,就将异常对象复制到 catch 形参中,catch 操作异常对象的不可一世,对形参所做的任何改变都只作用于副本,不会作用于异常对象本身。如果说明符是引用,则像引用形参一样,不存在单独的 catch 对象,catch 形参只是异常对象的另一名字。对 catch 形参所做的改变作用于异常对象。

Exception Specifiers and Inheritance
异常说明符与继承

Like a parameter declaration, an exception specifier for a base class can be used to catch an exception object of a derived type. Again, like a parameter declaration, the static type of the exception specifier determines the actions that the catch clause may perform. If the exception object thrown is of derived-class type but is handled by a catch that takes a base-class type, then the catch cannot use any members that are unique to the derived type.

像形参声明一样,基类的异常说明符可以用于捕获派生类型的异常对象,而且,异常说明符的静态类型决定 catch 子句可以执行的动作。如果被抛出的异常对象是派生类类型的,但由接受基类类型的 catch 处理,那么,catch 不能使用派生类特有的任何成员。

Usually, a catch clause that handles an exception of a type related by inheritance ought to define its parameter as a reference.

通常,如果 catch 子句处理因继承而相关的类型的异常,它就应该将自己的形参定义为引用。



If the catch parameter is a reference type, then the catch object accesses the exception object directly. The static type of the catch object and the dynamic type of the exception object to which it refers might differ. If the specifier is not a reference, then the catch object is a copy of the exception object. If the catch object in an object of the base type and the exception object has derived type, then the exception object is sliced down (Section 15.3.1, p. 577) to its base-class subobject.

如果 catch 形参是引用类型,catch 对象就直接访问异常对象,catch 对象的静态类型可以与 catch 对象所引用的异常对象的动态类型不同。如果异常说明符不是引用,则 catch 对象是异常对象的副本,如果 catch 对象是基类类型对象而异常对象是派生类型的,就将异常对象分割(第 15.3.1 节)为它的基类子对象。

Moreover, as we saw in Section 15.2.4 (p. 566), objects (as opposed to references) are not polymorphic. When we use a virtual function on an object rather than through a reference, the object's static and dynamic type are the same; the fact that the function is virtual makes no difference. Dynamic binding happens only for calls through a reference or pointer, not calls on an object.

而且,正如第 15.2.4 节所介绍的,对象(相对于引用)不是多态的。当通过对象而不是引用使用虚函数的时候,对象的静态类型和动态类型相同,函数是虚函数也一样。只有通过引用或指针调用时才发生动态绑定,通过对象调用不进行动态绑定。

Ordering of Catch Clauses Must Reflect Type Hierarchy
catch 子句的次序必须反映类型层次

When exception types are organized in class hierarchies, users may choose the level of granularity with which their applications will deal with an exception. For example, an application that merely wants to do cleanup and exit might define a single try block that encloses the code in main with a catch such as the following:

将异常类型组织成类层次的时候,用户可以选择应用程序处理异常的粒度级别。例如,只希望清除并退出的应用程序可以定义一个 try 块,该 try 块包围 main 函数中带有如下 catch 代码:

    catch(exception &e) {
        // do cleanup
        // print a message
        cerr << "Exiting: " << e.what() << endl;
        size_t status_indicator = 42;  // set and return an
        return(status_indicator);      // error indicator
    }

Other programs with more rigorous uptime requirements might need finer control over exceptions. Such applications will clear whatever caused the exception and continue processing.

有更严格实时需求的程序可能需要更好的异常控制,这样的应用程序将清除导致异常的一切并继续执行。

Because catch clauses are matched in the order in which they appear, programs that use exceptions from an inheritance hierarchy must order their catch clauses so that handlers for a derived type occurs before a catch for its base type.

因为 catch 子句按出现次序匹配,所以使用来自继承层次的异常的程序必须将它们的 catch 子句排序,以便 派生类型的处理代码出现在其基类类型的 catch 之前。

Multiple catch clauses with types related by inheritance must be ordered from most derived type to least derived.

带有因继承而相关的类型的多个 catch 子句,必须从最低派生类类到最高派生类型排序。



Exercises Section 17.1.3

Exercise 17.3:

Explain why this try block is incorrect. Correct it.

解释下面这个 try 块为什么不正确,并改正它。

          try {
              // use of the C++ standard library
          } catch(exception) {
              // ...
          } catch(const runtime_error &re) {
              // ...
          } catch(overflow_error eobj) { /* ... */ }


17.1.4. Rethrow

17.1.4. 重新抛出

It is possible that a single catch cannot completely handle an exception. After some corrective actions, a catch may decide that the exception must be handled by a function further up the chain of function calls. A catch can pass the exception out to another catch further up the list of function calls by rethrowing the exception. A rethrow is a throw that is not followed by a type or an expression:

有可能单个 catch 不能完全处理一个异常。在进行了一些校正行动之后,catch 可能确定该异常必须由函数调用链中更上层的函数来处理,catch 可以通过重新抛出将异常传递函数调用链中更上层的函数。重新抛出是后面不跟类型或表达式的一个 throw

    throw;

An empty throw rethrows the exception object. An empty throw can appear only in a catch or in a function called (directly or indirectly) from a catch. If an empty throw is encountered when a handler is not active, terminate is called.

throw 语句将重新抛出异常对象,它只能出现在 catch 或者从 catch 调用的函数中。如果在处理代码不活动时碰到空 throw,就调用 terminate 函数。

Although the rethrow does not specify its own exception, an exception object is still passed up the chain. The exception that is thrown is the original exception object, not the catch parameter. When a catch parameter is a base type, then we cannot know the actual type thrown by a rethrow expression. That type depends on the dynamic type of the exception object, not the static type of the catch parameter. For example, a rethrow from a catch with a parameter of base type might actually throw an object of the derived type.

虽然重新抛出不指定自己的异常,但仍然将一个异常对象沿链向上传递,被抛出的异常是原来的异常对象,而不是 catch 形参。当 catch 形参是基类类型的时候,我们不知道由重新抛出表达式抛出的实际类型,该类型取决于异常对象的动态类型,而不是 catch 形参的静态类型。例如,来自带基类类型形参 catch 的重新抛出,可能实际抛出一个派生类型的对象。

In general, a catch might change its parameter. If, after changing its parameter, the catch rethrows the exception, then those changes will be propagated only if the exception specifier is a reference:

一般而言,catch 可以改变它的形参。在改变它的形参之后,如果 catch 重新抛出异常,那么,只有当异常说明符是引用的时候,才会传播那些改变。

    catch (my_error &eObj) {        // specifier is a reference type
        eObj.status = severeErr;    // modifies the exception object
        throw; // the status member of the exception object is severeErr
    } catch (other_error eObj) {    // specifier is a nonreference type
        eObj.status = badErr;       // modifies local copy only
        throw; // the status member of the exception rethrown is unchanged
    }

17.1.5. The Catch-All Handler

17.1.5. 捕获所有异常的处理代码

A function may want to perform some action before it exits with a thrown exception, even though it cannot handle the exception that is thrown. Rather than provide a specific catch clause for every possible exception, and because we can't know all the exceptions that might be thrown, we can use a catch-all catch clause. A catch-all clause has the form (...). For example:

即使函数不能处理被抛出的异常,它也可能想要在随抛出异常退出之前执行一些动作。除了为每个可能的异常提供特定 catch 子句之外,因为不可能知道可能被抛出的所有异常,所以可以使用捕获所有异常 catch 子句的。捕获所有异常的 catch 子句形式为 (...)。例如:

     // matches any exception that might be thrown
     catch (...) {
         // place our code here
     }

A catch-all clause matches any type of exception.

捕获所有异常的 catch 子句与任意类型的异常都匹配。

A catch(...) is often used in combination with a rethrow expression. The catch does whatever local work can be done and then rethrows the exception:

catch(...) 经常与重新抛出表达式结合使用,catch 完成可做的所有局部工作,然后重新抛出异常:

    void manip() {
       try {
           // actions that cause an exception to be thrown
       }
       catch (...) {
           // work to partially handle the exception
           throw;
       }
    }

A catch(...) clause can be used by itself or as one of several catch clauses.

catch(...) 子句可以单独使用,也可以用在几个 catch 子句中间。

If a catch(...) is used in combination with other catch clauses, it must be last; otherwise, any catch clause that followed it could never be matched.

如果 catch(...) 与其他 catch 子句结合使用,它必须是最后一个,否则,任何跟在它后面的 catch 子句都将不能被匹配。



17.1.6. Function Try Blocks and Constructors

17.1.6. 函数测试块与构造函数

In general, exceptions can occur at any point in the program's execution. In particular, an exception might occur in a constructor, or while processing a constructor initializer. Constructor initializers are processed before the constructor body is entered. A catch clause inside the constructor body cannot handle an exception that might occur while processing a constructor initializer.

一般而言,异常可能发生在程序执行的任何一点。具体而言,异常可能发生在构造函数中,或者发生在处理构造函数初始化式的时候。在进入构造函数函数体之前处理构造函数初始化式,构造函数函数体内部的 catch 子句不能处理在处理构造函数初始化时可能发生的异常。

To handle an exception from a constructor initializer, we must write the constructor as a function try block. A function try block lets us associate a group of catch clauses with the function as a whole. As an example, we might wrap our Handle constructor from Chapter 16 in a try block to detect a failure in new:

为了处理来自构造函数初始化式的异常,必须将构造函数编写为函数 try。可以使用函数测试块将一组 catch 子句与函数联成一个整体。作为例子,可以将第十六章Handle 构造函数包装在一个用来检测 new 中失败的测试块当中:

    template <class T> Handle<T>::Handle(T *p)
    try : ptr(p), use(new size_t(1))
    {
         // empty function body
    }  catch(const std::bad_alloc &e)
           { handle_out_of_memory(e); }

Exercises Section 17.1.5

Exercise 17.4:

Given a basic C++ program,

对于如下的基本 C++ 程序

     int main() {
         // use of the C++ standard library
     }

modify main to catch any exception thrown by functions in the C++ standard library. The handlers should print the error message associated with the exception before calling abort (defined in the header cstdlib) to terminate main.

修改 main 函数以捕获由 C++ 标准库中函数抛出的任何异常。处理代码应该在调用 abort 函数(在头文件 cstdlib 中定义)终止 main 函数之前显示与异常相关的错误作息。

Exercise 17.5:

Given the following exception types and catch clauses, write a tHRow expression that creates an exception object that could be caught by each catch clause.

对于下面的异常类型以及 catch 子句,编写一个 throw 表达式,该表达式创建一个可被每个 catch 子句捕获的异常对象。

    (a) class exceptionType { };
        catch(exceptionType *pet) { }
    (b) catch(...) { }
    (c) enum mathErr { overflow, underflow, zeroDivide };
        catch(mathErr &ref) { }
    (d) typedef int EXCPTYPE;
        catch(EXCPTYPE) { }


Notice that the keyword try precedes the member initialization list, and the compound statement of the try block encompasses the constructor function body. The catch clause can handle exceptions thrown either from within the member initialization list or from within the constructor body.

注意,关键字 try 出现在成员初始化列表之前,并且测试块的复合语句包围了构造函数的函数体。catch 子句既可以处理从成员初始化列表中抛出的异常,也可以处理从构造函数函数体中抛出的异常。

The only way for a constructor to handle an exception from a constructor initializer is to write the constructor as a function try block.

构造函数要处理来自构造函数初始化式的异常,唯一的方法是将构造函数编写为函数测试块。



17.1.7. Exception Class Hierarchies

17.1.7. 异常类层次

Section 6.13 (p. 215) introduced the standard-library exception classes. What that section did not cover is that these classes are related by inheritance. The inheritance hierarchy is portrayed in Figure 17.1 on the following page.

第 6.13 节介绍过标准库异常类,但该节没有涵盖的是这些类是因继承而相关的,图 17.1 描绘了这个继承层次。

Figure 17.1. Standard exception Class Hierarchy
图 17.1. 标准 exception 类层次


The only operation the exception types define is a virtual member named what. That function returns a const char*. It typically returns the message used when constructing the exception object at the throw site. Because what is virtual if we catch a base-type reference, a call to the what function will execute the version appropriate to the dynamic type of the exception object.

exception 类型所定义的唯一操作是一个名为 what 的虚成员,该函数返回 const char* 对象,它一般返回用来在抛出位置构造异常对象的信息。因为 what 是虚函数,如果捕获了基类类型引用,对 what 函数的调用将执行适合异常对象的动态类型的版本。

Exception Classes for a Bookstore Application
用于书店应用程序的异常类

The standard exception classes can be used for quite a number of applications. In addition, applications often extend the exception hierarchy by deriving additional types from exception or one of the intermediate base classes. These newly derived classes can represent exception types specific to the application domain.

标准异常类可以用于许多应用程序,此外,应用程序还经常通过从 exception 类或者中间基类派生附加类型来扩充 exception 层次。这些新派生的类可以表示特定于应用程序领域的异常类型。

If we were building a real bookstore application, our classes would have been much more complex than the ones presented in this primer. One way in which they might be more elaborate would be in their handling of exceptions. In fact, we probably would have defined our own hierarchy of exceptions to represent application-specific problems that might arise. Our design might include classes such as

如果正在建立一个实际的书店应用程序,我们的类也许比本书给出的复杂得多,使它们更为精细的一个方法可能在于它们的异常处理。事实上,我们可能已经定义了自己的异常层次来表示可能出现的特定于应用程序的问题。我们的设计可能包括下面这样的类:

    // hypothetical exception classes for a bookstore application
    class out_of_stock: public std::runtime_error {
    public:
        explicit out_of_stock(const std::string &s):
                           std::runtime_error(s) { }
    };
    class isbn_mismatch: public std::logic_error {
    public:
        explicit isbn_mismatch(const std::string &s):
                              std::logic_error(s) { }
        isbn_mismatch(const std::string &s,
            const std::string &lhs, const std::string &rhs):
            std::logic_error(s), left(lhs), right(rhs) { }
        const std::string left, right;
        // Section 17.1.10 (p. 706) explains the destructor and why we need one
        virtual ~isbn_mismatch() throw() { }
    };

Here we defined our application-specific exception types by deriving them from the standard exception classes. As with any hierarchy, we can think of the exception classes as being organized into layers. As the hierarchy becomes deeper, each layer becomes a more specific exception. For example, the first and most general layer of the hierarchy is represented by class exception. All we know when we catch an object of this type is that something has gone wrong.

在这里,通过从标准异常类派生,定义了特定于应用程序的异常类型。像任何层次一样,可以认为异常类按层组织。随着层次的加深,每一层变得更特殊的异常。例如,层次中第一层即最一般的层由 exception 类代表,当捕获这一类型的对象时,我们所知道的只是有些地方出错了。

The second layer specializes exception into two broad categories: run-time or logic errors. Our bookstore exception classes represent an even more specialized layer. The class out_of_stock represents something that can go wrong at run time that is particular to our application. It would be used to signal that an order cannot be fulfilled. The isbn_mismatch exception is a more particular form of logic_error. In principle, a program could detect that the ISBNs don't match by calling same_isbn.

第二层将 exception 特化为两个大类:运行时错误和逻辑错误。我们的书店异常类表示更特化的层中的事件。out_of_stock 类表示可能在运行时出现问题的特定于应用程序的事情,可以用它发出不能履行某个订单的信号。isbn_mismatch 异常是从 logic_error 派生的更特殊的异常,原则上,程序可以通过调用 same_isbn 检测到不匹配的 ISBN。

Using Programmer-Defined Exception Types
使用程序员定义的异常类型

We use our own exception classes in the same way that we use one of the standard library classes. One part of the program throws an object of one of these types, and another part catches and handles the indicated problem. As an example, we might define the overloaded addition operator for our Sales_item class to throw an error of type isbn_mismatch if it detected that the ISBNs didn't match:

用和使用标准库类相同的方法使用自己的异常类。程序的一个部分抛出某个这些类型的对象,程序的另一部分捕获并处理指出的问题。例如,可以为 Sales_item 类定义重载加操作符,以便如果它检测到 ISBN 不匹配就抛出一个 isbn_mismatch 类型的错误:

     // throws exception if both objects do not refer to the same isbn
     Sales_item
     operator+(const Sales_item& lhs, const Sales_item& rhs)
     {
         if (!lhs.same_isbn(rhs))
             throw isbn_mismatch("isbn mismatch",
                                 lhs.book(), rhs.book());
         Sales_item ret(lhs);  // copy lhs into a local object that we'll return
         ret += rhs;           // add in the contents of rhs
         return ret;           // return ret by value
     }

Code that uses the addition operator could then detect this error, write an appropriate error message, and continue:

然后,使用加操作符的代码可以检测这个错误,写出适当的错误消息,并继续:

     // use hypothetical bookstore exceptions
     Sales_item item1, item2, sum;
     while (cin >> item1 >> item2) {    // read two transactions
         try {
             sum = item1 + item2;       // calculate their sum
             // use sum
         } catch (const isbn_mismatch &e) {
           cerr << e.what() << ": left isbn(" << e.left
                << ") right isbn(" << e.right << ")"
                << endl;
         }

17.1.8. Automatic Resource Deallocation

17.1.8. 自动资源释放

In Section 17.1.2 (p. 691) we saw that local objects are automatically destroyed when an exception occurs. The fact that destructors are run has important implication for the design of applications. It also is one (among many) reasons why we encourage the use of the standard library classes. Consider the following function:

第 17.1.2 节介绍过,在发生异常时自动撤销局部对象。运行析构函数这一事实对应用程序的设计具有重要含义,这也是为什么我们鼓励使用标准库类的原因。考虑下面的函数:

     void f()
     {
         vector<string> v;                   // local vector
         string s;
         while (cin >> s)
             v.push_back(s);                 // populate the vector
         string *p = new string[v.size()];   // dynamic array
         // remaining processing
         // it is possible that an exception occurs in this code
         // function cleanup is bypassed if an exception occurs
         delete [] p;
     }   // v destroyed automatically when the function exits

This function defines a local vector and dynamically allocates an array. Under normal execution, both the array and the vector are destroyed before the function exits. The array is freed by the last statement in the function, and the vector is automatically destroyed when the function ends.

这个函数定义了一个局部 vector 并动态分配了一个数组。在正常执行的情况下,数组和 vector 都在退出函数之前被撤销,函数中最后一个语句释放数组,在函数结束时自动撤销 vector

However, if an exception occurs inside the function, then the vector will be destroyed but the array will not be freed. The problem is that the array is not freed automatically. An exception that occurs after the new but before the corresponding delete leaves the array undestroyed. No matter when an exception occurs, we are guaranteed that the vector destructor is run.

但是,如果在函数内部发生异常,则将撤销 vector 而不会释放数组。问题在于数组不是自动释放的。在 new 之后但在 delete 之前发生的异常使得数组没有被撤销。不管何时发生异常,都保证运行 vector 析构函数。

Using Classes to Manage Resource Allocation
用类管理资源分配

The fact that destructors are run leads to an important programming technique that makes programs more exception safe. By exception safe, we mean that the programs operate correctly even if an exception occurs. In this case, the "safety" comes from ensuring that any resouce that is allocated is properly freed if an exception occurs.

对析构函数的运行导致一个重要的编程技术的出现,它使程序更为异常安全的。异常安全的意味着,即使发生异常,程序也能正确操作。在这种情况下,“安全”来自于保证“如果发生异常,被分配的任何资源都适当地释放”。

We can guarantee that resources are properly freed by defining a class to encapsulate the acquisition and release of a resource. This technique is often referred to as "resource allocation is initialization," often abreviated as RAII.

通过定义一个类来封闭资源的分配和释放,可以保证正确释放资源。这一技术常称为“资源分配即初始化”,简称 RAII。

The resource-managing class should be designed so that the constructor acquires the resource and the destructor frees it. When we want to allocate the resource, we define an object of that class type. If no exception occurs, then the resource will be freed when the object that acquired the resource goes out of scope. More importantly, if an exception occurs after the object is created but before it goes out of scope, then the compiler ensures that the object is destroyed as part of unwinding the scope in which the object was defined.

应该设计资源管理类,以便 构造函数分配资源而析构函数释放资源。想要分配资源的时候,就定义该类类型的对象。如果不发生异常,就在获得资源的对象超出作用域的进修释放资源。更为重要的是,如果在创建了对象之后但在它超出作用域之前发生异常,那么,编译器保证撤销该对象,作为展开定义对象的作用域的一部分。

The following class is a prototypical example in which the constructor acquires a resource and the destructor releases it:

下面的类是一个原型例子,其中构造函数分配资源而析构函数释放资源:

    class Resource {
    public:
        Resource(parms p): r(allocate(p)) { }
        ~Resource() { release(r); }
        // also need to define copy and assignment
    private:
        resource_type *r;           // resource managed by this type
        resource_type *allocate(parms p);     // allocate this resource
        void release(resource_type*);         // free this resource
    };

The Resource class is a type that allocates and deallocates a resource. It holds data member(s) that represent that resource. The constructor for Resource allocates the resource, and the destructor frees it. When we use this class

Resource 类是分配资源和回收资源的类型,它保存表示该资源的数据成员。Resource 的构造函数分配资源,而析构函数释放它。当使用这个类的时候

    void fcn()
    {
       Resource res(args);   // allocates resource_type
       // code that might throw an exception
       // if exception occurs, destructor for res is run automatically
       // ...
    }  // res goes out of scope and is destroyed automatically

the resource is automatically freed. If the function terminates normally, then the resource is freed when the Resource object goes out of scope. If the function is exited prematurely by an exception, the destructor for Resource is run by the compiler as part of the exception handling process.

自动释放资源。如果函数正常终止,就在 Resource 对象超出作用域时释放资源;如果函数因异常而提早退出,编译器就运行 Resource 的析构函数作为异常处理过程的一部分。

Programs in which exceptions are possible and that allocate resources should use classes to manage those resources. As described in this section, using classes to manage acquisition and deallocation ensures that resources are freed if an exception occurs.

可能存在异常的程序以及分配资源的程序应该使用类来管理那些资源。如本节所述,使用类管理分配和回收可以保证如果发生异常就释放资源。



Exercises Section 17.1.8

Exercise 17.6:

Given the following function, explain what happens when the exception occurs.

给定下面的函数,解释当发生异常时会发生什么。

     void exercise(int *b, int *e)
     {
         vector<int> v(b, e);
         int *p = new int[v.size()];
         ifstream in("ints");
         // exception occurs here
         // ...
     }

Exercise 17.7:

There are two ways to make the previous code exception-safe. Describe them and implement them.

有两种方法可以使上面的代码是异常安全的,描述并实现它们。


17.1.9. The auto_ptr Class

17.1.9. auto_ptr

The standard-library auto_ptr class is an example of the exception-safe "resource allocation is initialization" technique described in the previous subsection. The auto_ptr class is a template that takes a single type parameter. It provides exception safety for dynamically allocated objects. The auto_ptr class is defined in the memory header.

标准库的 auto_ptr 类是上一节中介绍的异常安全的“资源分配即初始化”技术的例子。auto_ptr 类是接受一个类型形参的模板,它为动态分配的对象提供异常安全。auto_ptr 类在头文件 memory 中定义。

Table 17.1. Class auto_ptr
表 17.1. auto_ptr

auto_ptr<T> ap;

Create an unbound auto_ptr named ap.

创建名为 ap 的未绑定的 auto_ptr 对象

auto_ptr<T> ap(p);

Create an auto_ptr named ap that owns the object pointed to by the pointer p. This constructor is explicit.

创建名为 apauto_ptr 对象,ap 拥有指针 p 指向的对象。该构造函数为 explicit

auto_ptr<T> ap1(ap2);

Create an auto_ptr named ap1 that holds the pointer originally stored in ap2. TRansfers ownership to ap1; ap2 becomes an unbound auto_ptr.

创建名为 ap1auto_ptr 对象,ap1 保存原来存储在 ap2 中的指针。将所有权转给 ap1ap2 成为未绑定的 auto_ptr 对象

ap1 = ap2

Transfers ownership from ap2 to ap1. Deletes the object to which ap1 points and makes ap1 point to the object to which ap2 points, making ap2 unbound.

将所有权 ap2 转给 ap1。删除 ap1 指向的对象并且使 ap1 指向 ap2 指向的对象,使 ap2 成为未绑定的

~ap

Destructor. Deletes the object to which ap points.

析构函数。删除 ap 指向的对象

*ap

Returns a reference to the object to which ap is bound.

返回对 ap 所绑定的对象的引用

ap->

Returns the pointer that ap holds.

返回 ap 保存的指针

ap.reset(p)

If the pointer p is not the same value as ap holds, then it deletes the object to which ap points and binds ap to p.

如果 pap 的值不同,则删除 ap 指向的对象并且将 ap 绑定到 p

ap.release()

Returns the pointer that ap had held and makes ap unbound.

返回 ap 所保存的指针并且使 ap 成为未绑定的

ap.get()

Returns the pointer that ap holds.

返回 ap 保存的指针


auto_ptr can be used only to manage single objects returned from new. It does not manage dynamically allocated arrays.

auto_ptr 只能用于管理从 new 返回的一个对象,它不能管理动态分配的数组。



As we'll see, auto_ptr has unusual behavior when copied or assigned. As a result, auto_ptrs may not be stored in the library container types.

正如我们所见,当 auto_ptr 被复制或赋值的时候,有不寻常的行为,因此,不能将 auto_ptrs 存储在标准库容器类型中。

An auto_ptr may hold only a pointer to an object and may not be used to point to a dynamically allocated array. Using an auto_ptr to point to a dynamically allocated array results in undefined run-time behavior.

auto_ptr 对象只能保存一个指向对象的指针,并且不能用于指向动态分配的数组,使用 auto_ptr 对象指向动态分配的数组会导致未定义的运行时行为。

Each auto_ptr is either unbound or it points to an object. When an auto_ptr points to an object, it can be said to "own" that object. When the auto_ptr goes out of scope or is otherwise destroyed, then the dynamically allocated object to which the auto_ptr points is automatically deallocated.

每个 auto_ptr 对象绑定到一个对象或者指向一个对象。当 auto_ptr 对象指向一个对象的时候,可以说它“拥有”该对象。当 auto_ptr 对象超出作用域或者另外撤销的时候,就自动回收 auto_ptr 所指向的动态分配对象。

Using auto_ptr for Exception-Safe Memory Allocation
为异常安全的内存分配使用 auto_ptr

If memory is acquired through a normal pointer and an exception occurs before a delete is executed, then that memory won't be freed automatically:

如果通过常规指针分配内在,而且在执行 delete 之前发生异常,就不会自动释放该内存:

     void f()
     {
        int *ip = new int(42);     // dynamically allocate a new object
        // code that throws an exception that is not caught inside f
        delete ip;                 // return the memory before exiting
     }

If an exception happens between the new and the delete, and if that exception is not caught locally, then the delete will not be executed. The memory will never be returned.

如果在 newdelete 之间发生异常,并且该异常不被局部捕获,就不会执行 delete,则永不回收该内存。

If we use an auto_ptr instead, the memory will be freed automatically, even if the block is exited prematurely:

如果使用一个 auto_ptr 对象来代替,将会自动释放内存,即使提早退出这个块也是这样:

     void f()
     {
        auto_ptr<int> ap(new int(42)); // allocate a new object
        // code that throws an exception that is not caught inside f
     }
              // auto_ptr freed automatically when function ends

In this case, the compiler ensures that the destructor for ap is run before the stack is unwound past f.

在这个例子中,编译器保证在展开栈越过 f 之前运行 ap 的析构函数。

auto_ptr Is a Template and Can Hold Pointers of Any Type
auto_ptr 是可以保存任何类型指针的模板

The auto_ptr class is a template taking a single type parameter. That type names the type of the object to which the auto_ptr may be bound. Thus, we can create auto_ptrs of any type:

auto_ptr 类是接受单个类型形参的模板,该类型指定 auto_ptr 可以绑定的对象的类型,因此,可以创建任何类型的 auto_ptrs

    auto_ptr<string> ap1(new string("Brontosaurus"));

Binding an auto_ptr to a Pointer
auto_ptr 绑定到指针

In the most common case, we initialize an auto_ptr to the address of an object returned by a new expression:

在最常见的情况下,将 auto_ptr 对象初始化为由 new 表达式返回的对象的地址:

    auto_ptr<int> pi(new int(1024));

This statement initializes pi to the address of the object created by the new expression. This new expression initializes that int to the value 1,024.

这个语句将 pi 初始化为由 new 表达式创建的对象的地址,这个 new 表达式将对象初始化为 1024。

The constructor that takes a pointer is an explicit (Section 12.4.4, p. 462) constructor, so we must use the direct form of initialization to create an auto_ptr:

接受指针的构造函数为 explicit第 12.4.4 节)构造函数,所以必须使用初始化的直接形式来创建 auto_ptr 对象:

    // error: constructor that takes a pointer is explicit and can't be used implicitly
    auto_ptr<int> pi = new int(1024);
    auto_ptr<int> pi(new int(1024)); // ok: uses direct initialization

The object created by the new expression to which pi refers is deleted automatically when pi goes out of scope. If pi is a local object, the object to which pi refers is deleted at the end of the block in which pi is defined. If an exception occurs, then pi also goes out of scope. The destructor for pi will be run automatically as part of handling the exception. If pi is a global object, the object to which pi refers is deleted at the end of the program.

pi 所指的由 new 表达式创建的对象在超出作用域时自动删除。如果 pi 是局部对象,pi 所指对象在定义 pi 的块的末尾删除;如果发生异常,则 pi 也超出作用域,析构函数将自动运行 pi 的析构函数作为异常处理的一部分;如果 pi 是全局对象,就在程序末尾删除 pi 引用的对象。

Using an auto_ptr
使用 auto_ptr 对象

Suppose we wish to access a string operation. With an ordinary string pointer, we'd do the following:

假设希望访问 string 操作。用普通 string 指针,像下面这样做:

    string *pstr_type = new string("Brontosaurus");
    if (pstr_type->empty())
        // oops, something wrong

The auto_ptr class defines overloaded versions of the dereference (*) and arrow (->) operators (Section 14.6, p. 523). Because auto_ptr defines these operators, we can use an auto_ptr in some ways that are similar to using a built-in pointer:

auto_ptr 类定义了解引用操作符(*)和箭头操作符(->)的重载版本(第 14.6 节),因为 auto_ptr 定义了这些操作符,所以可以用类似于使用内置指针的方式使用 auto_ptr 对象:

     // normal pointer operations for dereference and arrow
     *ap1 = "TRex";       // assigns a new value to the object to which ap1 points
     string s = *ap1;     // initializes s as a copy of the object to which ap1 points
     if (ap1->empty())    // runs empty on the string to which ap1 points

The primary purpose of auto_ptr is to support ordinary pointerlike behavior while ensuring that the object to which an auto_ptr object refers is automatically deleted. As we'll see, the fact that objects are automatically deleted leads to significant differences between auto_ptrs and ordinary pointers with respect to how we copy and access their address value.

auto_ptr 的主要目的,在保证自动删除 auto_ptr 对象引用的对象的同时,支持普通指针式行为。正如我们所见,自动删除该对象这一事实导致在怎样复制和访问它们的地址值方面,auto_ptrs 与普通指针明显不同。

Copy and Assignment on auto_ptr Are Destructive Operations
auto_ptr 对象的复制和赋值是破坏性操作

There is a crucially important difference between how auto_ptr and built-in pointers treat copy and assignment. When we copy an auto_ptr or assign its value to another auto_ptr, ownership of the underlying object is transferred from the original to the copy. The original auto_ptr is reset to an unbound state.

auto_ptr 和内置指针对待复制和赋值有非常关键的重要区别。当复制 auto_ptr 对象或者将它的值赋给其他 auto_ptr 对象的时候,将基础对象的所有权从原来的 auto_ptr 对象转给副本,原来的 auto_ptr 对象重置为未绑定状态。



Copying (or assigning) ordinary pointers copies (assigns) the address. After the copy (assignment), both pointers point to the same object. After copying (or assigning) auto_ptrs, the original points to no object and the new auto_ptr (left-hand auto_ptr) owns the underlying object:

复制(或者赋值)普通指针是复制(或者赋值)地址,在复制(或者赋值)之后,两个指针指向同一对象。在复制(或者赋值)auto_ptrs 对象之后,原来的 auto_ptr 对象不指向对象而新的 auto_ptr(左边的 auto_ptr 对象)拥有基础对象:

     auto_ptr<string> ap1(new string("Stegosaurus"));
     // after the copy ap1 is unbound
     auto_ptr<string> ap2(ap1);  // ownership transferred from ap1 to ap2

When we copy or assign an auto_ptr, the right-hand auto_ptr relinquishes all responsibility for the underlying object and is reset to be an unbound auto_ptr. In our example, it is ap2 that deletes the string object, and not ap1. After the copy, ap1 no longer refers to any object.

当复制 auto_ptr 对象或者对 auto_ptr 对象赋值的时候,右边的 auto_ptr 对象让出对基础对象的所有职责并重置为未绑定的 auto_ptr 对象之后,在上例中,删除 string 对象的是 ap2 而不是 ap1,在复制之后,ap1 不再指向任何对象。

Unlike other copy or assignment operations, auto_ptr copy and assignment change the right-hand operand. As a result, both the left- and right-hand operands to assignment must be modifiable lvalues.

与其他复制或赋值操作不同,auto_ptr 的复制和赋值改变右操作数,因此,赋值的左右操作数必须都是可修改的左值。

Assignment Deletes the Object Pointed To by the Left Operand
赋值删除左操作数指向的对象

In addition to transferring ownership from the right-hand to the left-hand operand, assignment also deletes the object to which the left-hand operand originally referredprovided that the two objects are different. As usual, self-assignment has no effect.

除了将所有权从右操作数转给左操作数之外,赋值还删除左操作数原来指向的对象——假如两个对象不同。通常自身赋值没有效果。

     auto_ptr<string> ap3(new string("Pterodactyl"));
     // object pointed to by ap3 is deleted and ownership transferred from ap2 to ap3;
     ap3 = ap2;  // after the assignment, ap2 is unbound

After the assignment of ap2 to ap3,

ap2 赋给 ap3 之后:

  • the object to which ap3 had pointed is deleted;

    删除了 ap3 指向的对象。

  • ap3 is set to point to the object to which ap2 pointed; and

    ap3 置为指向 ap2 所指的对象。

  • ap2 is an unbound auto_ptr.

    ap2 是未绑定的 auto_ptr 对象。

Because copy and assignment are destructive operations, auto_ptrs cannot be stored in the standard containers. The library container classes require that two objects be equal after a copy or assignment. This requirement is not met by auto_ptr. If we assign ap2 to ap1, then after the assignment ap1 != ap2. Similarly for copy.

因为复制和赋值是破坏性操作,所以auto_ptrs不能auto_ptr 对象存储在标准容器中。标准库的容器类要求在复制或赋值之后两个对象相等,auto_ptr 不满足这一要求,如果将 ap2 赋给 ap1,则在赋值之后 ap1 != ap2,复制也类似。



The Default auto_ptr Constructor
auto_ptr 的默认构造函数

If no initializer is given, the auto_ptr is unbound; it doesn'trefertoanobject:

如果不给定初始式,auto_ptr 对象是未绑定的,它不指向对象:

     auto_ptr<int> p_auto;  // p_autodoesn't refer to any object

By default, the internal pointer value of an auto_ptr is set to 0. Dereferencing an unbound auto_ptr has the same effect as dereferencing an unbound pointerthe program is in error and what happens is undefined:

默认情况下,auto_ptr 的内部指针值置为 0。对未绑定的 auto_ptr 对象解引用,其效果与对未绑定的指针解引用相同——程序出错并且没有定义会发生什么:

    *p_auto = 1024;  // error: dereference auto_ptr that doesn't point to an object

Testing an auto_ptr
测试 auto_ptr 对象

To check whether a pointer is unbound, we can test the pointer directly in a condition, which has the effect of determining whether the pointer is 0. In contrast, we cannot test an auto_ptr directly.

为了检查指针是否未绑定,可以在条件中直接测试指针,效果是确定指针是否为 0。相反,不能直接测试 auto_ptr 对象:

    // error: cannot use an auto_ptr as a condition
    if (p_auto)
        *p_auto = 1024;

The auto_ptr type does not define a conversion to a type that can be used as a condition. Instead, to test the auto_ptr, we must use its get member, which returns the underlying pointer contained in the auto_ptr:

auto_ptr 类型没有定义到可用作条件的类型的转换,相反,要测试 auto_ptr 对象,必须使用它的 get 成员,该成员返回包含在 auto_ptr 对象中的基础指针:

    // revised test to guarantee p_auto refers to an object
    if (p_auto.get())
        *p_auto = 1024;

To determine whether the auto_ptr object refers to an object, we can compare the return from get with 0.

为了确定 auto_ptr 是否指向一个对象,可以将 get 的返回值与 0 比较。

get should be used only to interrogate an auto_ptr or to use the returned pointer value. get should not be used as an argument to create another auto_ptr.

应该只用 get 询问 auto_ptr 对象或者使用返回的指针值,不能用 get 作为创建其他 auto_ptr 对象的实参。



Using get member to initialize another auto_ptr violates the class design principle that only one auto_ptr holds a given pointer at any one time. If two auto_ptrs hold the same pointer, then the pointer will be deleted twice.

使用 get 成员初始化其他 auto_ptr 对象违反 auto_ptr 类设计原则:在任意时刻只有一个 auto_ptrs 对象保存给定指针,如果两个 auto_ptrs 对象保存相同的指针,该指针就会被 delete 两次。

The reset Operation
reset 操作

Another difference between auto_ptr and a built-in pointer is that we cannot assign an address (or other pointer) directly to an auto_ptr:

auto_ptr 对象与内置指针的另一个区别是,不能直接将一个地址(或者其他指针)赋给 auto_ptr 对象:

     p_auto = new int(1024); // error: cannot assign a pointer to an auto_ptr

Instead, we must call reset to change the pointer:

相反,必须调用 reset 函数来改变指针:

     // revised test to guarantee p_auto refers to an object
     if (p_auto.get())
         *p_auto = 1024;
     else
         // reset p_auto to a new object
         p_auto.reset(new int(1024));

To unset the auto_ptr object, we could pass 0 to reset.

要复位 auto_ptr 对象,可以将 0 传给 reset 函数。

Calling reset on an auto_ptr deletes the object (if any) to which the auto_ptr refers before binding the auto_ptr to another object. However, just as self-assignment has no effect, if we call reset on the same pointer that the auto_ptr already holds, then there is no effect; the object is not deleted.

调用 auto_ptr 对象的 reset 函数时,在将 auto_ptr 对象绑定到其他对象之前,会删除 auto_ptr 对象所指向的对象(如果存在)。但是,正如自身赋值是没有效果的一样,如果调用该 auto_ptr 对象已经保存的同一指针的 reset 函数,也没有效果,不会删除对象。



Exercises Section 17.1.9

Exercise 17.8:

Which of the following auto_ptr declarations are illegal or likely to result in subsequent program error? Explain what the problem is with each one.

下面 auto_ptr 声明中,哪些是不合法的或者可能导致随后的程序错误?解释每个声明的问题。

     int ix = 1024, *pi = &ix, *pi2 = new int(2048);
     typedef auto_ptr<int> IntP;
     (a) IntP p0(ix);               (b) IntP p1(pi);
     (c) IntP p2(pi2);              (d) IntP p3(&ix);
     (e) IntP p4(new int(2048));    (f) IntP p5(p2.get());

Exercise 17.9:

Assuming ps is a pointer to string, what is the difference, if any, between the following two invocations of assign (Section 9.6.2, p. 339)? Which do you think is preferable? Why?

假定 ps 是一个指向 string 的指针,如果有的话,下面两个 assign第 9.6.2 节)调用有什么不同?你认为哪个更好,为什么?

     (a) ps.get()->assign("Danny");  (b) ps->assign("Danny");


17.1.10. Exception Specifications

17.1.10. 异常说明

When looking at an ordinary function declaration, it is not possible to determine what exceptions the function might throw. However, it can be useful to know whether and which exceptions a function might throw in order to write appropriate catch clauses. An exception specification specifies that if the function throws an exception, the exception it throws will be one of the exceptions included in the specification, or it will be a type derived from one of the listed exceptions.

查看普通函数声明的时候,不可能确定该函数会抛出什么异常,但是,为了编写适当的 catch 子句,了解函数是否抛出异常以及会抛出哪种异常是很有用的。异常说明指定,如果函数抛出异常,被抛出的异常将是包含在该说明中的一种,或者是从列出的异常中派生的类型。

Caution: Auto_ptr Pitfalls

警告:Auto_ptr 缺陷

The auto_ptr class template provides a measure of safety and convenience for handling dynamically allocated memory. To use auto_ptr correctly, we must adhere to the restrictions that the class imposes:

auto_ptr 炻模板为处理动态分配的内存提供了安全性和便利性的尺度。要正确地使用 auto_ptr 类,必须坚持该类强加的下列限制:

  1. Do not use an auto_ptr to hold a pointer to a statically allocated object. Otherwise, when the auto_ptr itself is destroyed, it will attempt to delete a pointer to a nondynamically allocated object, resulting in undefined behavior.

    不要使用 auto_ptr 对象保存指向静态分配对象的指针,否则,当 auto_ptr 对象本身被撤销的时候,它将试图删除指向非动态分配对象的指针,导致未定义的行为。

  2. Never use two auto_ptrs to refer to the same object. One obvious way to make this mistake is to use the same pointer to initialize or to reset two different auto_ptr objects. A more subtle way to make this mistake would be to use the result from get on one auto_ptr to initialize or reset another.

    永远不要使用两个 auto_ptrs 对象指向同一对象,导致这个错误的一种明显方式是,使用同一指针来初始化或者 reset 两个不同的 auto_ptr 对象。另一种导致这个错误的微妙方式可能是,使用一个 auto_ptr 对象的 get 函数的结果来初始化或者 reset 另一个 auto_ptr 对象。

  3. Do not use an auto_ptr to hold a pointer to a dynamically allocated array. When the auto_ptr is destroyed, it frees only a single objectit uses the plain delete operator, not the array delete [] operator.

    不要使用 auto_ptr 对象保存指向动态分配数组的指针。当 auto_ptr 对象被删除的时候,它只释放一个对象——它使用普通 delete 操作符,而不用数组的 delete [] 操作符。

  4. Do not store an auto_ptr in a container. Containers require that the types they hold define copy and assignment to behave similarly to how those operations behave on the built-in types: After the copy (or assignment), the two objects must have the same value. auto_ptr does not meet this requirement.

    不要将 auto_ptr 对象存储在容器中。容器要求所保存的类型定义复制和赋值操作符,使它们表现得类似于内置类型的操作符:在复制(或者赋值)之后,两个对象必须具有相同值,auto_ptr 类不满足这个要求。


Defining an Exception Specification
定义异常说明

An exception specification follows the function parameter list. An exception specification is the keyword throw followed by a (possibly empty) list of exception types enclosed in parentheses:

异常说明跟在函数形参表之后。一个异常说明在关键字 throw 之后跟着一个(可能为空的)由圆括号括住的异常类型列表:

     void recoup(int) throw(runtime_error);

This declaration says that recoup is a function taking an int, and returningvoid. If recoup throws an exception, that exception will be a runtime_error or an exception of a type derived from runtime_error.

这个声明指出,recoup 是接受 int 值的函数,并返回 void。如果 recoup 抛出一个异常,该异常将是 runtime_error 对象,或者是由 runtime_error 派生的类型的异常。

An empty specification list says that the function does not throw any exception:

空说明列表指出函数不抛出任何异常:

     void no_problem() throw();

An exception specification is part of the function's interface. The function definition and any declarations of the function must have the same specification.

异常说明是函数接口的一部分,函数定义以及该函数的任意声明必须具有相同的异常说明。

If a function declaration does not specify an exception specification, the function can throw exceptions of any type.

如果一个函数声明没有指定异常说明,则该函数可以抛出任意类型的异常。



Violating the Exception Specification
违反异常说明

Unfortunately, it is not possible to know at compile time whether or which exceptions a program will throw. Violations of a function's exception specification can be detected only at run time.

但是,不可能在编译时知道程序是否抛出异常以及会抛出哪些异常,只有在运行时才能检测是否违反函数异常说明。

If a function throws an exception not listed in its specification, the library function unexpected is invoked. By default, unexpected calls terminate, which ordinarily aborts the program.

如果函数抛出了没有在其异常说明中列出的异常,就调用标准库函数 unexpected。默认情况下,unexpected 函数调用 terminate 函数,terminate 函数一般会终止程序。

The compiler cannot and does not attempt to verify exception specifications at compile time.

在编译的时候,编译器不能也不会试图验证异常说明。



Even if a casual reading of a function's code indicates that it might throw an exception missing from the specification, the compiler will not complain:

即使对函数代码的偶然阅读指明,它可能抛出异常说明中没有的异常,编译器也不会给出提示:

     void f() throw()          // promise not to throw any exception
     {
         throw exception();    // violates exception specification
     }

Instead, the compiler generates code to ensure that unexpected is called if an exception violating the exception specification is thrown.

相反,编译器会产生代码以便保证:如果抛出了一个违反异常说明的异常,就调用 unexpected 函数。

Specifying that the Function Does Not Throw
确定函数不抛出异常

Because an exception specification cannot be checked at compile time, the practical utility of exception specifications is often limited.

因为不能在编译时检查异常说明,异常说明的应用通常是有限的。

One important case when an exception specification is useful is if a function can guarantee that it will not throw any exceptions.

异常说服有用的一种重要情况是,如果函数可以保证不会抛出任何异常。



Specifying that a function will not throw any exceptions can be helpful both to users of the function and to the compiler: Knowing that a function will not throw simplifies the task of writing exception-safe code that calls that function. We can know that we need not worry about exceptions when calling it. Moreover, if the compiler knows that no exceptions will be thrown, it can perform optimizations that are suppressed for code that might throw.

确定函数将不抛出任何异常,对函数的用户和编译器都有所帮助:知道函数不抛出异常会简化编写调用该函数的异常安全的代码的工作,我们可以知道在调用函数时不必担心异常,而且,如果编译器知道不会抛出异常,它就可以执行被可能抛出异常的代码所抑制的优化。

Exception Specifications and Member Functions
异常说明与成员函数

As with nonmember functions, an exception specification on a member function declaration follows the function parameter list. For example, the class bad_alloc from the C++ standard library is defined so that all its member functions have an empty exception specification. These members promise not to throw an exception:

像非成员函数一样,成员函数声明的异常说明跟在函数形参表之后。例如,C++ 标准库中的 bad_alloc 类定义为所有成员都有空异常说明,这些成员承诺不抛出异常:

     // ilustrative definition of library bad_alloc class
     class bad_alloc : public exception {
     public:
         bad_alloc() throw();
         bad_alloc(const bad_alloc &) throw();
         bad_alloc & operator=(const
         bad_alloc &) throw();
         virtual ~bad_alloc() throw();
         virtual const char* what() const throw();
     };

Notice that the exception specification follows the const qualifier in const member function declarations.

注意,在 const 成员函数声明中,异常说明跟在 const 限定符之后。

Exception Specifications and Destructors
异常说明与析构函数

In Section 17.1.7 (p. 697) we showed two hypothetical bookstore application exception classes. The isbn_mismatch class defines its destructor as

第 17.1.7 节介绍了两个假设的书店应用程序异常类,isbn_mismatch 类将析构函数定义为:

     class isbn_mismatch: public std::logic_error {
     public:
         virtual ~isbn_mismatch() throw() { }
     };

and said that we would explain this usage here.

并说明我们会在这里解释这种用法。

The isbn_mismatch class inherits from logic_error, which is one of the standard exception classes. The destructors for the standard exception classes include an empty throw() specifier; they promise that they will not throw any exceptions. When we inherit from one of these classes, then our destructor must also promise not to throw any exceptions.

isbn_mismatch 类从 logic_error 类继承而来,logic_error 是一个标准异常类,该标准异常类的析构函数包含空 throw() 说明符,它们承诺不抛出任何异常。当继承这两个类中的一个时,我们的析构函数也必须承诺不抛出任何异常。

Our out_of_stock class had no members, and so its synthesized destructor does nothing that might throw an exception. Hence, the compiler can know that the synthesized destructor will abide by the promise not to throw.

out_of_stock 类没有成员,所以它的合成析构函数不做任何可能抛出异常的事情,因此,编译器可以知道合成析构函数将遵守不抛出异常的承诺。

The isbn_mismatch class has two members of class string, which means that the synthesized destructor for isbn_mismatch calls the string destructor. The C++ standard stipulates that string destructor, like any other library class destructor, will not throw an exception. However, the library destructors do not define exception specifications. In this case, we know, but the compiler doesn't, that the string destructor won't throw. We must define our own destructor to reinstate the promise that the destructor will not throw.

isbn_mismatch 类有两个 string 类成员,这意味着 isbn_mismatch 的合成析构函数调用 string 析构函数。C++ 标准保证,string 析构函数像任意其他标准库类析构函数一样,不抛出异常。但是,标准库的析构函数没有定义异常说明,在这种情况下,我们知道,但编译器不知道,string 析构函数将不抛出异常。我们必须定义自己的析构函数来恢复析构函数不抛出异常的承诺。

Exception Specifications and Virtual Functions
异常说明与虚函数

A virtual function in a base class may have an exception specification that differs from the exception specification of the corresponding virtual in a derived class. However, the exception specification of a derived-class virtual function must be either equally or more restrictive than the exception specification of the corresponding base-class virtual function.

基类中虚函数的异常说明,可以与派生类中对应虚函数的异常说明不同。但是,派生类虚函数的异常说明必须与对应基类虚函数的异常说明同样严格,或者比后者更受限。

This restriction ensures that when a pointer to a base-class type is used to call a derived virtual function, the exception specification of the derived class adds no new exceptions to those that the base said could be thrown. For example,

这个限制保证,当使用指向基类类型的指针调用派生类虚函数的时候,派生类的异常说明不会增加新的可抛出异常。例如:

     class Base {
     public:
         virtual double f1(double) throw ();
         virtual int f2(int) throw (std::logic_error);
         virtual std::string f3() throw
               (std::logic_error, std::runtime_error);
     };
     class Derived : public Base {
     public:
         // error: exception specification is less restrictive than Base::f1's
         double f1(double) throw (std::underflow_error);

         // ok: same exception specification as Base::f2
         int f2(int) throw (std::logic_error);
         // ok: Derived f3 is more restrictive
         std::string f3() throw ();
     };

The declaration of f1 in the derived class is an error because its exception specification adds an exception to those listed in the version of f1 in the base class. The reason that the derived class may not add to the specfication list is users of the hierarchy should be able to write code that depends on the specification list. If a call is made through a base pointer or reference, then only the exceptions specified in the base should be of concern to a user of these classes.

派生类中 f1 的声明是错误的,因为它的异常说明在基类 f1 版本列出的异常中增加了一个异常。派生类不能在异常说明列表中增加异常,原因在于,继承层次的用户应该能够编写依赖于该说明列表的代码。如果通过基类指针或引用进行函数调用,那么,这些类的用户所涉及的应该只是在基类中指定的异常。

By restricting which exceptions the derived classes will throw to those listed by the base class, we can write our code knowing what exceptions we must handle. Our code can rely on the fact that the list of exceptions in the base class is a superset of the list of exceptions that a derived-class version of the virtual might throw. As an example, when calling f3, we know we need to handle only logic_error or runtime_error:

通过派生类抛出的异常限制为由基类所列出的那些,在编写代码时就可以知道必须处理哪些异常。代码可以依赖于这样一个事实:基类中的异常列表是虚函数的派生类版本可以抛出的异常列表的超集。例如,当调用 f3 的时候,我们知道只需要处理 logic_errorruntime_error

     // guarantees not to throw exceptions
     void compute(Base *pb) throw()
     {
         try {
             // may throw exception of type std::logic_error
             // or std::runtime_error
             pb->f3();
         } catch (const logic_error &le)   { /* ... */ }
           catch (const runtime_error &re) { /* ... */ }
     }

The function compute uses the specification in the base class in deciding what exceptions it might need to catch.

在确定可能需要捕获什么异常的时候,compute 函数使用基类中的异常说明。

17.1.11. Function Pointer Exception Specifications

17.1.11. 函数指针的异常说明

An exception specification is part of a function type. As such, exception specifications can be provided in the definition of a pointer to function:

异常说明是函数类型的一部分。这样,也可以在函数指针的定义中提供异常说明:

     void (*pf)(int) throw(runtime_error);

This declaration says that pf points to a function that takes an int, returns avoid, and that can throw exceptions only of type runtime_error. If no specification is provided, then the pointer may point at a function with matching type that could throw any kind of exception.

这个声明是说,pf 指向接受 int 值的函数,该函数返回 void 对象,该函数只能抛出 runtime_error 类型的异常。如果不提供异常说明,该指针就可以指向能够抛出任意类型异常的具有匹配类型的函数。

When a pointer to function with an exception specification is initialized from (or assigned to) another pointer (or to the address of a function), the exception specifications of both pointers do not have to be identical. However, the specification of the source pointer must be at least as restrictive as the specification of the destination pointer

在用另一指针初始化带异常说明的函数的指针,或者将后者赋值给函数地址的时候,两个指针的异常说明不必相同,但是,源指针的异常说明必须至少与目标指针的一样严格。

     void recoup(int) throw(runtime_error);
     // ok: recoup is as restrictive as pf1
     void (*pf1)(int) throw(runtime_error) = recoup;
     // ok: recoup is more restrictive than pf2
     void (*pf2)(int) throw(runtime_error, logic_error) = recoup;
     // error: recoup is less restrictive than pf3
     void (*pf3)(int) throw() = recoup;
     // ok: recoup is more restrictive than pf4
     void (*pf4)(int) = recoup;

The third initialization is an error. The pointer declaration says that pf3 points to a function that will not throw any exceptions. However, recoup says it can throw exceptions of type runtime_error. The recoup function throws exception types beyond those specified by pf3. The recoup function is not a valid initializer for pf3, and a compile-time error is issued.

第三个初始化是错误的。指针声明指出,pf3 指向不抛出任何异常的函数,但是,recoup 函数指出它能抛出 runtime_error 类型的异常,recoup 函数抛出的异常类型超出了 pf3 所指定的,对 pf3 而言,recoup 函数不是有效的初始化式,并且会引发一个编译时错误。

Exercises Section 17.1.11

Exercise 17.10:

What exceptions can a function throw if it has an exception specification of the form throw()? If it has no exception specification?

如果函数有形如 throw() 的异常说明,它能抛出什么异常?如果没有异常说明呢?

Exercise 17.11:

Which, if either, of the following initializations is in error? Why?

如果有,下面哪个初始化是错误的?为什么?

     void example() throw(string);
     (a) void (*pf1)() = example;
     (b) void (*pf2)() throw() = example;

Exercise 17.12:

Which exceptions might the following functions throw?

下面函数可以抛出哪些异常?

     (a) void operate() throw(logic_error);
     (b) int op(int) throw(underflow_error, overflow_error);
     (c) char manip(string) throw();
     (d) void process();


Team LiB
Previous Section Next Section