Выбрать главу

//{-bor}

#include <exception>    // for std::bad_exception

#include <iostream>

#include <cstdio>

using namespace std;

// Exception classes:

class A {};

class B {};

// terminate() handler

void my_thandler() {

  cout << "terminate called\n";

  exit(0);

}

// unexpected() handlers

void my_uhandler1() {

  throw A();

}

void my_uhandler2() {

  throw;

}

// If we embed this throw statement in f or g,

// the compiler detects the violation and reports

// an error, so we put it in its own function.

void t() {

  throw B();

}

void f() throw(A) {

  t();

}

void g() throw(A, bad_exception) {

  t();

}

int main() {

  set_terminate(my_thandler);

  set_unexpected(my_uhandler1);

  try {

    f();

  }

  catch (A&) {

    cout << "caught an A from f\n";

  }

  set_unexpected(my_uhandler2);

  try {

    g();

  }

  catch (bad_exception&) {

    cout << "caught a bad_exception from g\n";

  }

  try {

    f();

  }

  catch (...) {

    cout << "This will never print\n";

  }

} ///:~

The my_uhandler1( ) handler throws an acceptable exception (A), so execution resumes at the first catch, which succeeds. The my_uhandler2( ) handler does not throw a valid exception (B), but since g specifies bad_exception, the B exception is replaced by a bad_exception object, and the second catch also succeeds. Since f does not include bad_exception in its specification, my_thandler( ) is called as a terminate handler. Thus, the output from this program is as follows:.

caught an A from f

caught a bad_exception from g

terminate called

Better exception specifications?

You may feel that the existing exception specification rules aren’t very safe, and that

void f();

should mean that no exceptions are thrown from this function. If the programmer wants to throw any type of exception, you might think he or she should have to say.

void f() throw(...); // Not in C++

This would surely be an improvement because function declarations would be more explicit. Unfortunately, you can’t always know by looking at the code in a function whether an exception will be thrown—it could happen because of a memory allocation, for example. Worse, existing functions written before exception handling was introduced may find themselves inadvertently throwing exceptions because of the functions they call (which might be linked into new, exception-throwing versions). Hence, the uninformative situation whereby.

void f();

means, "Maybe I’ll throw an exception, maybe I won’t." This ambiguity is necessary to avoid hindering code evolution. If you want to specify that f throws no exceptions, use the empty list, as in:.

void f() throw();

Exception specifications and inheritance

Each public function in a class essentially forms a contract with the user; if you pass it certain arguments, it will perform certain operations and/or return a result. The same contract must hold true in derived classes; otherwise the expected "is-a" relationship between derived and base classes is violated. Since exception specifications are logically part of a function’s declaration, they too must remain consistent across an inheritance hierarchy. For example, if a member function in a base class says it will only throw an exception of type A, an override of that function in a derived class must not add any other exception types to the specification list, because that would result in unexpected exceptions for the user, breaking any programs that adhere to the base class interface. You can, however, specify fewer exceptions or none at all, since that doesn’t require the user to do anything differently. You can also specify anything that "is-a" A in place of A in the derived function’s specification. Here’s an example.

// C01:Covariance.cpp

// Compile Only!

//{-msc}

#include <iostream>

using namespace std;

class Base {

public:

  class BaseException {};

  class DerivedException : public BaseException {};

  virtual void f() throw (DerivedException) {

    throw DerivedException();

  }

  virtual void g() throw (BaseException) {

    throw BaseException();

  }

};

class Derived : public Base {

public:

  void f() throw (BaseException) {

    throw BaseException();

  }

  virtual void g() throw (DerivedException) {

    throw DerivedException();

  }

};

A compiler should flag the override of Derived::f( ) with an error (or at least a warning) since it changes its exception specification in a way that violates the specification of Base::f( ). The specification for Derived::g( ) is acceptable because DerivedException "is-a" BaseException (not the other way around). You can think of Base/Derived and BaseException/DerivedException as parallel class hierarchies; when you are in Derived, you can replace references to BaseException in exception specifications and return values with DerivedException. This behavior is called covariance  (since both sets of classes vary down their respective hierarchies together). (Reminder from Volume 1: parameter types are not covariant—you are not allowed to change the signature of an overridden virtual function.).

When not to use exception specifications

If you peruse the function declarations throughout the Standard C++ library, you’ll find that not a single exception specification occurs anywhere! Although this might seem strange, there is a good reason for this seeming incongruity: the library consists mainly of templates, and you never know what a generic might do. For example, suppose you are developing a generic stack template and attempt to affix an exception specification to your pop function, like this:

T pop() throw(logic_error);