B. Stroustrup - The C++ Programming Language (794319), страница 82
Текст из файла (страница 82)
For example (§iso.20.2.2):template<class T, size_t N>void swap(T (&a)[N], T (&b)[N]) noexcept(noexcept(swap(∗a, ∗b)));13.5.1.3 Exception SpecificationsIn older C++ code, you may find exception specifications. For example:void f(int) throw(Bad,Worse); // may only throw Bad or Worse exceptionsvoid g(int) throw();// may not throwAn empty exception specification throw() is defined to be equivalent to noexcept (§13.5.1.1).
Thatis, if an exception is thrown, the program terminates.The meaning of a nonempty exception specification, such as throw(Bad,Worse), is that if thefunction (here f()) throws any exception that is not mentioned in the list or publicly derived from anexception mentioned there, an unexpected handler is called. The default effect of an unexpectedexception is to terminate the program (§30.4.1.3). A nonempty throw specification is hard to usewell and implies potentially expensive run-time checks to determine if the right exception isthrown.
This feature has not been a success and is deprecated. Don’t use it.If you want to dynamically check which exceptions are thrown, use a try-block.13.5.2 Catching ExceptionsConsider:void f(){try {throw E{};}catch(H) {// when do we get here?}}The handler is invoked:[1] If H is the same type as E[2] If H is an unambiguous public base of E[3] If H and E are pointer types and [1] or [2] holds for the types to which they refer[4] If H is a reference and [1] or [2] holds for the type to which H refersIn addition, we can add const to the type used to catch an exception in the same way that we can368Exception HandlingChapter 13add it to a function parameter.
This doesn’t change the set of exceptions we can catch; it onlyrestricts us from modifying the exception caught.In principle, an exception is copied when it is thrown (§13.5). The implementation may apply awide variety of strategies for storing and transmitting exceptions. It is guaranteed, however, thatthere is sufficient memory to allow new to throw the standard out-of-memory exception, bad_alloc(§11.2.3).Note the possibility of catching an exception by reference. Exception types are often defined aspart of class hierarchies to reflect relationships among the kinds of errors they represent. For examples, see §13.5.2.3 and §30.4.1.1. The technique of organizing exception classes into hierarchies iscommon enough for some programmers to prefer to catch every exception by reference.The {} in both the try-part and a catch-clause of a try-block are real scopes.
Consequently, if aname is to be used in both parts of a try-block or outside it, that name must be declared outside thetry-block. For example:void g(){int x1;try {int x2 = x1;// ...}catch (Error) {++x1;// OK++x2;// error: x2 not in scopeint x3 = 7;// ...}catch(...) {++x3;// error: x3 not in scope// ...}++x1;++x2;++x3;// OK// error: x2 not in scope// error: x3 not in scope}The ‘‘catch everything’’ clause, catch(...), is explained in §13.5.2.2.13.5.2.1 RethrowHaving caught an exception, it is common for a handler to decide that it can’t completely handlethe error. In that case, the handler typically does what can be done locally and then throws theexception again.
Thus, an error can be handled where it is most appropriate. This is the case evenwhen the information needed to best handle the error is not available in a single place, so that therecovery action is best distributed over several handlers.
For example:Section 13.5.2.1Rethrow369void h(){try {// ... code that might throw an exception ...}catch (std::exception& err) {if (can_handle_it_completely) {// ... handle it ...return;}else {// ... do what can be done here ...throw;// rethrow the exception}}}A rethrow is indicated by a throw without an operand. A rethrow may occur in a catch-clause or ina function called from a catch-clause. If a rethrow is attempted when there is no exception torethrow, std::terminate() (§13.5.2.5) will be called. A compiler can detect and warn about some, butnot all, such cases.The exception rethrown is the original exception caught and not just the part of it that wasaccessible as an exception. For example, had an out_of_range been thrown, h() would catch it as aplain exception, but throw; would still rethrow it as an out_of_range.
Had I written throw err; insteadof the simpler throw;, the exception would have been sliced (§17.5.1.4) and h()’s caller could nothave caught it as an out_of_range.13.5.2.2 Catch Every ExceptionIn <stdexcept>, the standard library provides a small hierarchy of exception classes with a commonbase exception (§30.4.1.1). For example:void m(){try {// ... do something ...}catch (std::exception& err) {// ... cleanup ...throw;}// handle every standard-librar y exception}This catches every standard-library exception. However, the standard-library exceptions are justone set of exception types. Consequently, you cannot catch every exception by catching std::exception.
If someone (unwisely) threw an int or an exception from some application-specific hierarchy,it would not be caught by the handler for std::exception&.However, we often need to deal with every kind of exception. For example, if m() is supposedto leave some pointers in the state in which it found them, then we can write code in the handler to370Exception HandlingChapter 13give them acceptable values.
As for functions, the ellipsis, ..., indicates ‘‘any argument’’ (§12.2.4),so catch(...) means ‘‘catch any exception.’’ For example:void m(){try {// ... something ...}catch (...) {// handle every exception// ... cleanup ...throw;}}13.5.2.3 Multiple HandlersA try-block may have multiple catch-clauses (handlers). Because a derived exception can be caughtby handlers for more than one exception type, the order in which the handlers are written in a trystatement is significant. The handlers are tried in order.
For example:void f(){try {// ...}catch (std::ios_base::failure) {// ... handle any iostream error (§30.4.1.1) ...}catch (std::exception& e) {// ... handle any standard-librar y exception (§30.4.1.1) ...}catch (...) {// ... handle any other exception (§13.5.2.2) ...}}The compiler knows the class hierarchy, so it can warn about many logical mistakes.
For example:void g(){try {// ...}catch (...) {// ... handle every exception (§13.5.2.2) ...}catch (std::exception& e) {// ...handle any standard librar y exception (§30.4.1.1) ...}Section 13.5.2.3Multiple Handlers371catch (std::bad_cast) {// ... handle dynamic_cast failure (§22.2.1) ...}}Here, the exception is never considered. Even if we removed the ‘‘catch-all’’ handler, bad_castwouldn’t be considered because it is derived from exception. Matching exception types to catchclauses is a (fast) run-time operation and is not as general as (compile-time) overload resolution.13.5.2.4 Function try-BlocksThe body of a function can be a try-block.
For example:int main()try{// ... do something ...}catch (...} {// ... handle exception ...}For most functions, all we gain from using a function try-block is a bit of notational convenience.However, a try-block allows us to deal with exceptions thrown by base-or-member initializers inconstructors (§17.4). By default, if an exception is thrown in a base-or-member initializer, theexception is passed on to whatever invoked the constructor for the member’s class. However, theconstructor itself can catch such exceptions by enclosing the complete function body – includingthe member initializer list – in a try-block.
For example:class X {vector<int> vi;vector<string> vs;// ...public:X(int,int);// ...};X::X(int sz1, int sz2)try:vi(sz1), // construct vi with sz1 intsvs(sz2), // construct vs with sz2 strings{// ...}catch (std::exception& err) { // exceptions thrown for vi and vs are caught here// ...}372Exception HandlingChapter 13So, we can catch exceptions thrown by member constructors. Similarly, we can catch exceptionsthrown by member destructors in a destructor (though a destructor should never throw). However,we cannot ‘‘repair’’ the object and return normally as if the exception had not happened: an exception from a member constructor means that the member may not be in a valid state.
Also, othermember objects will either not be constructed or already have had their destructors invoked as partof the stack unwinding.The best we can do in a catch-clause of a function try-block for a constructor or destructor is tothrow an exception. The default action is to rethrow the original exception when we ‘‘fall off theend’’ of the catch-clause (§iso.15.3).There are no such restrictions for the try-block of an ordinary function.13.5.2.5 TerminationThere are cases where exception handling must be abandoned for less subtle error-handling techniques. The guiding principles are:• Don’t throw an exception while handling an exception.• Don’t throw an exception that can’t be caught.If the exception-handling implementation catches you doing either, it will terminate your program.If you managed to have two exceptions active at one time (in the same thread, which you can’t),the system would have no idea which of the exceptions to try to handle: your new one or the one itwas already trying to handle.
Note that an exception is considered handled immediately upon entryinto a catch-clause. Rethrowing an exception (§13.5.2.1) or throwing a new exception from withina catch-clause is considered a new throw done after the original exception has been handled. Youcan throw an exception from within a destructor (even during stack unwinding) as long as youcatch it before it leaves the destructor.The specific rules for calling terminate() are (§iso.15.5.1)• When no suitable handler was found for a thrown exception• When a noexcept function tries to exit with a throw• When a destructor invoked during stack unwinding tries to exit with a throw• When code invoked to propagate an exception (e.g., a copy constructor) tries to exit with athrow• When someone tries to rethrow (throw;) when there is no current exception being handled• When a destructor for a statically allocated or thread-local object tries to exit with a throw• When an initializer for a statically allocated or thread-local object tries to exit with a throw• When a function invoked as an atexit() function tries to exit with a throwIn such cases, the function std::terminate() is called.