Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
1715
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Chapter 15

for (size_t i = 0; i < myInts.size(); i++) { cerr << myInts[i] << “ “;

}

cout << endl;

return (0);

}

Although not shown in this example, set_terminate() returns the old terminate_handler when it sets the new one. The terminate_handler applies program-wide, so it’s considered good style to reset the old terminate_handler when you have completed the code that needed the new terminate_handler. In this case, the entire program needs the new terminate_handler, so there’s no point in resetting it.

Although it’s important to know about set_terminate(), it’s not a very effective exception-handling approach. We recommend trying to catch and handle each exception individually in order to provide more precise error handling.

Throw Lists

C++ allows you to specify the exceptions a function or method intends to throw. This specification is called the throw list or the exception specification. Here is the readIntegerFile() function from the earlier example with the proper throw list:

void readIntegerFile(const string& fileName, vector<int>& dest)

throw (invalid_argument, runtime_error)

{

// Remainder of the function is the same as before

}

The throw list simply lists the types of exceptions that can be thrown from the function. Note that the throw list must also be provided for the function prototype:

void readIntegerFile(const string& fileName, vector<int>& dest)

throw (invalid_argument, runtime_error);

Unlike const, the exception specification is not part of the function or method signature. You cannot overload a function based solely on different exceptions in the throw list.

If a function or method specifies no throw list, it can throw any exception. You’ve already seen this behavior in the previous implementation of the readIntegerFile() function. If you want to specify that a function or method throws no exceptions, you need to write an empty throw list explicitly like this:

void readIntegerFile(const string& fileName, vector<int>& dest)

throw ();

If this behavior seems backward to you, you’re not alone. However, it’s best just to accept it and move on.

A function without a throw list can throw exceptions of any type. A function with an empty throw list shouldn’t throw any exception.

412

Handling Errors

Unexpected Exceptions

Unfortunately, the throw list is not enforced at compile time in C++. Code that calls readIntegerFile() does not need to catch the exceptions listed in the throw list. This behavior is different from that in other languages, such as Java, which requires a function or method to catch exceptions or declare them in their own function or method throw lists.

Additionally, you could implement readIntegerFile() like this:

void readIntegerFile(const string& fileName, vector<int>& dest)

throw (invalid_argument, runtime_error)

{

throw (5);

}

Even though the throw list states that readIntegerFile() doesn’t throw an int, this code, which obviously throws an int, compiles and runs. However, it won’t do what you want. Suppose that you write this main() function, assuming that you can catch the int:

int main(int argc, char** argv)

{

vector<int> myInts;

const string fileName = “IntegerFile.txt”;

try {

readIntegerFile(fileName, myInts); } catch (int x) {

cerr << “Caught int\n”;

}

}

When this program runs and readIntegerFile() throws the int exception, the program terminates. It does not allow main() to catch the int. However, you can change this behavior.

Throw lists don’t prevent functions from throwing unlisted exception types, but they prevent the exception from leaving the function.

When a function throws an exception that is not listed in its throw list, C++ calls a special function unexpected(). The built-in implementation of unexpected() simply calls terminate(). However, just as you can set your own terminate_handler, you can set your own unexpected_handler. Unlike in the terminate_handler, you can actually do something other than just terminate the program in the unexpected_handler. Your version of the function must either throw a new exception or terminate the program — it can’t just exit the function normally. If it throws a new exception, that exception will be substituted for the unexpected exception as if the new one had been throw originally. If this substituted exception is also not listed in the throw list, the program will do one of two things. If the throw list for the function specifies bad_exception, then bad_exception will be thrown. Otherwise, the program will terminate. Custom implementations of unexpected() are normally used to convert unexpected exceptions into expected exceptions. For example, you could write a version of unexpected() like this:

413

Chapter 15

void myUnexpected()

{

cout << “Unexpected exception!\n”; throw runtime_error(“”);

}

This code converts an unexpected exception to a runtime_error exception, which the function readIntegerFile() has in its throw list.

You could set this unexpected exception handler in main() with the set_unexpected function. Like set_terminate(), set_unexpected() returns the current handler. The unexpected() function applies program-wide, not just to this function, so you should reset the handler when you are done with the code that needed your special handler:

int main(int argc, char** argv)

{

vector<int> myInts;

const string fileName = “IntegerFile.txt”;

unexpected_handler old_handler = set_unexpected(myUnexpected); try {

readIntegerFile(fileName, myInts); } catch (const invalid_argument& e) {

cerr << “Unable to open file “ << fileName << endl; exit (1);

} catch (const runtime_error& e) {

cerr << “Error reading file “ << fileName << endl; exit (1);

} catch (int x) {

cout << “Caught int\n”;

}

set_unexpected(old_handler);

// Remainder of function omitted

}

Now main() handles any exception thrown from readIntegerFile() by converting it to a runtime_ error. However, as with set_terminate(), we recommend using this capability judiciously.

unexpected(), set_unexpected(), and bad_exception are all declared in the <exception> header file.

Changing the Throw List in Overridden Methods

When you override a virtual method in a subclass, you can change the throw list as long as you make it more restrictive than the throw list in the superclass. The following changes qualify as more restrictive:

Removing exceptions from the list

Adding subclasses of exceptions that appear in the superclass throw list The following changes do not qualify as more restrictive:

Adding exceptions to the list that are not subclasses of exceptions in the superclass throw list

Removing the throw list entirely

414

Handling Errors

If you change throw lists when you override methods, remember that any code that called the superclass version of the method must be able to call the subclass version. Thus, you can’t add exceptions.

For example, suppose that you have the following superclass:

class Base

{

public:

virtual void func() throw(exception) { cout << “Base!\n”; }

};

You could write a subclass that overrides func() and specifies that it doesn’t throw any exceptions:

class Derived : public Base

{

public:

virtual void func() throw() { cout << “Derived!\n”; }

};

You could also override func() such that it throws a runtime_error as well as an exception, because runtime_error is a subclass of exception.

class Derived : public Base

{

public:

virtual void func() throw(exception, runtime_error)

{ cout << “Derived!\n”; }

};

However, you cannot remove the throw list entirely, because that means func() could throw any exception.

Suppose Base looked like this:

class Base

{

public:

virtual void func() throw(runtime_error) { cout << “Base!\n”; }

};

You cannot then override func() in Derived with a throw list like this:

class Derived : public Base

{

public:

virtual void func() throw(exception) { cout << “Derived!\n”; } // ERROR!

};

exception is a superclass of runtime_error, so you cannot substitute an exception for a runtime_error.

415