Professional C__ - Marc Gregoire [164]
However, you cannot remove the throw list entirely, because that means func() could throw any exception.
As a second example, suppose Base looked like this:
class Base
{
public:
virtual void func() throw(runtime_error) { cout << "Base!\n"; }
};
Code snippet from ThrowListsVirtualMethods\Broken.cpp
Then you cannot override func() in Derived with a throw list like this:
class Derived : public Base
{
public:
virtual void func() throw(exception) // ERROR!
{ cout << "Derived!\n"; }
};
Code snippet from ThrowListsVirtualMethods\Broken.cpp
exception is a superclass of runtime_error, so you cannot substitute an exception for a runtime_error.
Are Throw Lists Useful?
Given the opportunity to specify the behavior of a function in its prototype, it seems wasteful not to take advantage of it. The exceptions thrown from a particular function are an important part of its interface, and should be documented as well as possible.
Unfortunately, most of the C++ code in use today, including the Standard Library, does not follow this advice. That makes it difficult for you to determine which exceptions can be thrown when you use this code. Additionally, it is impossible to specify the exception characteristics of templatized functions and methods. When you don’t even know what types will be used to instantiate the template, you have no way to determine the exceptions that methods of those types can throw. As a final problem, the throw list syntax and enforcement is somewhat obscure.
Thus, we leave the decision up to you.
EXCEPTIONS AND POLYMORPHISM
As described earlier, you can actually throw any type of exception. However, classes are the most useful types of exceptions. In fact, exception classes are usually written in a hierarchy, so that you can employ polymorphism when you catch the exceptions.
The Standard Exception Hierarchy
You’ve already seen several exceptions from the C++ standard exception hierarchy: exception, runtime_error, and invalid_argument. Figure 10-3 shows the complete hierarchy:
FIGURE 10-3
All of the exceptions thrown by the C++ Standard Library are objects of classes in this hierarchy. Each class in the hierarchy supports a what() method that returns a const char* string describing the exception. You can use this string in an error message.
All the exception classes except for the base exception require you to set in the constructor the string that will be returned by what(). That’s why you have to specify a string in the constructors for runtime_error and invalid_argument. Now that you know what the strings are used for, you can make them more useful. Here is an example where the string is used to pass the full error message back to the caller:
void readIntegerFile(const string& fileName, vector throw(invalid_argument, runtime_error) { ifstream istr; int temp; istr.open(fileName.c_str()); if (istr.fail()) { // We failed to open the file: throw an exception. string error = "Unable to open file " + fileName; throw invalid_argument(error); } // Read the integers one by one and add them to the vector. while (istr >> temp) { dest.push_back(temp); } if (istr.eof()) { // We reached the end-of-file. istr.close(); } else { // Some other error. Throw an exception. istr.close(); string error = "Unable to read file " + fileName; throw runtime_error(error); } } int main() { // Code omitted try { readIntegerFile(fileName, myInts); } catch (const invalid_argument& e) { cerr << e.what() << endl; return 1; } catch (const runtime_error& e) { cerr << e.what() << endl; return 1; } // Code omitted } Code snippet from ExceptionsAndPolymorphism\UsingWhat.cpp Catching Exceptions in a Class Hierarchy The most exciting feature of exception hierarchies is that you can catch exceptions polymorphically. For example, if you look at the two catch statements in main() following the call to readIntegerFile(), you can see that they are identical except for the exception class that they handle. Conveniently, invalid_argument