Professional C__ - Marc Gregoire [165]
try {
readIntegerFile(fileName, myInts);
} catch (const exception& e) {
cerr << e.what() << endl;
return 1;
}
Code snippet from ExceptionsAndPolymorphism\CatchingPolymorphicallyCorrectOne.cpp
The catch statement for an exception reference matches any subclasses of exception, including both invalid_argument and runtime_error. Note that the higher in the exception hierarchy that you catch exceptions, the less specific is your error handling. You should generally catch exceptions at as specific a level as possible.
When you catch exceptions polymorphically, make sure to catch them by reference. If you catch exceptions by value, you can encounter slicing, in which case you lose information from the object. See Chapter 8 for details on slicing.
When more than one catch clause is used, the catch clauses are matched in syntactic order as they appear in your code; the first one that matches, wins. If one catch is more inclusive than a later one, it will match first, and the more restrictive one, which comes later, will not be executed at all. Therefore, you should place your catch clauses from most restrictive to least restrictive order. For example, suppose that you want to catch invalid_argument from readIntegerFile() explicitly, but leave the generic exception match for any other exceptions. The correct way to do so is like this:
try {
readIntegerFile(fileName, myInts);
} catch (const invalid_argument& e) { // List exception subclass first.
// Take some special action for invalid filenames.
} catch (const exception& e) { // Now list exception
cerr << e.what() << endl;
return 1;
}
Code snippet from ExceptionsAndPolymorphism\CatchingPolymorphicallyCorrectTwo.cpp
The first catch statement catches invalid_argument exceptions, and the second catches any other exceptions of type exception. However, if you reverse the order of the catch statements, you don’t get the same result:
try {
readIntegerFile(fileName, myInts);
} catch (const exception& e) { // BUG: catching superclass first!
cerr << e.what() << endl;
return 1;
} catch (const invalid_argument& e) {
// Take some special action for invalid filenames.
}
Code snippet from ExceptionsAndPolymorphism\CatchingPolymorphicallyIncorrect.cpp
With this order, any exception of a class that subclasses exception is caught by the first catch statement; the second will never be reached. Some compilers issue a warning in this case, but you shouldn’t count on it.
Writing Your Own Exception Classes
There are two advantages to writing your own exception classes.
1. The number of exceptions in the C++ Standard Library is limited. Instead of using an exception class with a generic name, such as runtime_error, you can create classes with names that are more meaningful for the particular errors in your program.
2. You can add your own information to these exceptions. The exceptions in the standard hierarchy allow you to set only an error string. You might want to pass different information in the exception.
We recommend that all the exception classes that you write inherit directly or indirectly from the standard exception class. If everyone on your project follows that rule, you know that every exception in the program will be a subclass of exception (assuming that you aren’t using third-party libraries that break this rule). This guideline makes exception handling via polymorphism significantly easier.
For example, invalid_argument and runtime_error don’t capture very well the file opening and reading errors in readIntegerFile(). You can define your own error hierarchy for file errors, starting with a generic FileError class:
class FileError : public runtime_error
{
public:
FileError(const string& fileIn)
: runtime_error(""), mFile(fileIn) {}
virtual const char* what() const noexcept { return mMsg.c_str(); }
string getFileName() { return mFile; }
protected:
string mFile, mMsg;
};
Code snippet from