Chapter 10

Handling Errors

WHAT’S IN THIS CHAPTER?

  • How to handle errors in C++, including pros and cons of exceptions
  • The syntax of exceptions
  • Exception class hierarchies and polymorphism
  • Stack unwinding and cleanup
  • Common error-handling situations

Inevitably, your C++ programs will encounter errors. The program might be unable to open a file, the network connection might go down, or the user might enter an incorrect value, to name a few possibilities. The C++ language provides a feature called exceptions to handle these exceptional but not unexpected situations.

The code examples in this book so far have virtually always ignored error conditions for brevity. This chapter rectifies that simplification by teaching you how to incorporate error handling into your programs from their beginnings. It focuses on C++ exceptions, including the details of their syntax, and describes how to employ them effectively to create well-designed error-handling programs.

ERRORS AND EXCEPTIONS

No program exists in isolation; they all depend on external facilities such as interfaces with the operating system, networks and file systems, external code such as third-party libraries, and user input. Each of these areas can introduce situations which require responding to problems which may be encountered. These potential problems can be referred to with the general term exceptional situations. Even perfectly written programs encounter errors and exceptional situations. Thus, anyone who writes a computer program must include error-handling capabilities. Some languages, such as C, do not include many specific language facilities for error handling. Programmers using these languages generally rely on return values from functions and other ad hoc approaches. Other languages, such as Java, enforce the use of a language feature called exceptions as an error-handling mechanism. C++ lies between these extremes. It provides language support for exceptions, but does not require their use. However, you can’t ignore exceptions entirely in C++ because a few basic facilities, such as memory allocation routines, use them.

What Are Exceptions, Anyway?

Exceptions are a mechanism for a piece of code to notify another piece of code of an “exceptional” situation or error condition without progressing through the normal code paths. The code that encounters the error throws the exception, and the code that handles the exception catches it. Exceptions do not follow the fundamental rule of step-by-step execution to which you are accustomed. When a piece of code throws an exception, the program control immediately stops executing code step by step and transitions to the exception handler, which could be anywhere from the next line in the same function to several function calls up the stack. If you like sports analogies, you can think of the code that throws an exception as an outfielder throwing a baseball back to the infield, where the nearest infielder (closest exception handler) catches it. Figure 10-1 shows a hypothetical stack of three function calls. Function A() has the exception handler. It calls function B(), which calls function C(), which throws the exception.

Figure 10-2 shows the handler catching the exception. The stack frames for C() and B() have been removed, leaving only A().

Some people who have used C++ for years are surprised to learn that C++ supports exceptions. Programmers tend to associate exceptions with languages like Java, in which they are much more visible. However, C++ has full-fledged support for exceptions.

Why Exceptions in C++ Are a Good Thing

As mentioned earlier, run-time errors in programs are inevitable. Despite that fact, error handling in most C and C++ programs is messy and ad hoc. The de facto C error-handling standard, which was carried over into many C++ programs, uses integer function return codes and the errno macro to signify errors. Each thread has its own errno value. errno acts as a thread-global integer variable that functions can use to communicate errors back to calling functions.

Unfortunately, the integer return codes and errno are used inconsistently. Some functions might choose to return 0 for success and −1 for an error. If they return −1, they also set errno to an error code. Other functions return 0 for success and nonzero for an error, with the actual return value specifying the error code. These functions do not use errno. Still others return 0 for failure instead of for success, presumably because 0 always evaluates to false in C and C++.

These inconsistencies can cause problems because programmers encountering a new function often assume that its return codes are the same as other similar functions. That is not always true. On Solaris 9, there are two different libraries of synchronization objects: the POSIX version and the Solaris version. The function to initialize a semaphore in the POSIX version is called sem_init(), and the function to initialize a semaphore in the Solaris version is called sema_init(). As if that weren’t confusing enough, the two functions handle error codes differently! sem_init() returns –1 and sets errno on error, while sema_init() returns the error code directly as a positive integer, and does not set errno.

Another problem is that the return type of functions in C++ can only be of one type, so if you need to return both an error and a value, you must find an alternative mechanism. One solution is to return a std::pair or std::tuple, an object that you can use to store two or more types. They are discussed in later STL chapters. Another choice is to define your own struct or class that contains several values, and return an instance of that struct or class from your function. Yet another option is to return the value or error through a reference parameter or to make the error code one possible value of the return type, such as a nullptr pointer. In all these solutions, the caller is responsible to explicitly check for any errors returned from the function and if it doesn’t handle the error itself, it should propagate the error to its caller. Unfortunately, this will often result in the loss of critical details about the error.

C programmers may be familiar with a mechanism known as setjmp()/longjmp(). This mechanism cannot be used correctly in C++, because it bypasses scoped destructors on the stack. You should avoid it at all cost, even in C programs; therefor this book will not explain the details of how to use it.

Exceptions provide an easier, more consistent, and safer mechanism for error handling. There are several specific advantages of exceptions over the ad hoc approaches in C and C++.

  • When return codes are used as an error reporting mechanism, a defect is that a caller may choose to ignore the return codes, or process them only locally without propagating them upwards, even though higher-level callers need to know there was a lower-level failure. Exceptions cannot be ignored: If your program fails to catch an exception, it will terminate.
  • When integer return codes are used, they generally do not contain sufficient information. You can use exceptions to pass as much information as you want from the code that finds the error to the code that handles it. Exceptions can also be used to communicate information other than errors, though many programmers consider that an abuse of the exception mechanism.
  • Exception handling can skip levels of the call stack. That is, a function can handle an error that occurred several function calls down the stack, without error-handling code in the intermediate functions. Return codes require each level of the call stack to clean up explicitly after the previous level.

Why Exceptions in C++ Are a Bad Thing

In some compilers (fewer and fewer these days), exception handling added a tiny amount of overhead to any function that had an exception handler. In modern compilers, this overhead is so small or even non-existing, so that you can ignore it. Do not assume that the urban legend about exceptions introducing serious overhead is true; you have to check it out in your compiler.

Exception handling is not enforced in C++. For example, in Java a function that does not specify a list of possible exceptions that it can throw is not allowed to throw any exceptions. In C++, it is just the opposite: a function that does not specify a list of exceptions can throw any exception it wants! Additionally, the exception list is not enforced at compile time in C++, meaning that the exception list of a function can be violated at run time. Note that some tools, such as using /Analyze with Microsoft VC++, will check exceptions and report potential problems.

Our Recommendation

We recommend exceptions as a useful mechanism for error handling. We feel that the structure and error-handling formalization that exceptions provide outweigh the less desirable aspects. Thus, the remainder of this chapter focuses on exceptions. Also, many popular libraries, such as the STL and Boost use exceptions, so you need to be prepared to handle them. We recommend you read the rest of this chapter, because you should not be surprised by the experience of having an exception thrown.

EXCEPTION MECHANICS

Exceptional situations arise frequently in file input and output. The following is a function to open a file, read a list of integers from the file, and store the integers in the supplied std::vector data structure. A vector is a dynamic array. You can add elements to it by using the push_back() method, and access them with array notation.

image
void readIntegerFile(const string& fileName, vector<int>& dest)
{
    ifstream istr;
    int temp;
    istr.open(fileName.c_str());
    // Read the integers one by one and add them to the vector.
    while (istr >> temp) {
        dest.push_back(temp);
    }
}

Code snippet from ReadIntegerFileNoExceptionHandling.cpp

The following line keeps reading values from the ifstream until the end of the file is reached or until an error occurs.

while (istr >> temp) {

This works because the >> operator returns a reference to the ifstream object itself. Additionally, ifstream provides a bool() conversion operator implemented as follows:

return !fail();

If the >> operator encounters an error, it will set the fail bit of the ifstream object. In that case, the bool() conversion operator will return false and the while loop will terminate. Streams are discussed in more detail in Chapter 15.

You might use readIntegerFile() like this:

image
vector<int> myInts;
const string fileName = "IntegerFile.txt";
readIntegerFile(fileName, myInts);
for (size_t i = 0; i < myInts.size(); i++) {
    cout << myInts[i] << " ";
}
cout << endl;

Code snippet from ReadIntegerFileNoExceptionHandling.cpp

The lack of error handling in these functions should jump out at you. The rest of this section shows you how to add error handling with exceptions.

Throwing and Catching Exceptions

Using exceptions consists of providing two parts in your program: a try/catch construct, to handle an exception, and a throw statement, that throws an exception. Both must be present in some form to make exceptions work. However, in many cases, the throw happens deep inside some library (including the C++ run time) and the programmer never sees it, but still has to react to it using a try/catch construct.

The try/catch construct looks as follows:

try {
    // ... code which may result in an exception being thrown
} catch (exception-type1 exception-name) {
    // ... code which responds to the exception of type 1
} catch (exception-type2 exception-name) {
    // ... code which responds to the exception of type 2
}
// ... remaining code

The code which may result in an exception being thrown might contain a throw directly, or might be calling a function which either directly throws an exception or calls, by some unknown number of layers of calls, a function which throws an exception.

If no exception is thrown, the code in the catch blocks is not executed, and the “remaining code” which follows will follow the last statement executed in the try block.

If an exception is thrown, any code following the throw or following the call which resulted in the throw, is not executed, but control immediately goes to the right catch block depending on the type of the exception that is thrown.

If the catch block does not do a control transfer, for example by returning a value, throwing a new exception or rethrowing the exception, then the “remaining code” is executed after the last statement of that catch block.

The simplest example to demonstrate exception handling is avoiding divide-by-zero. This example throws an exception of type invalid_argument which requires the <stdexcept> header.

image
int SafeDivide(int num, int den)
{
    if (den == 0)
        throw invalid_argument("Divide by zero");
    return num / den;
}
int main()
{
    try {
        cout << SafeDivide(5, 2) << endl;
        cout << SafeDivide(10, 0) << endl;
        cout << SafeDivide(3, 3) << endl;
    } catch (const invalid_argument& e) {
        cout << "Caught exception: " << e.what() << endl;
    }
    return 0;
}

Code snippet from SafeDivideSafeDivide.cpp

The output is as follows:

2
Caught exception: Divide by zero

throw is a keyword in C++, and is the only way to throw an exception. The invalid_argument() part of the throw line means that you are constructing a new object of type invalid_argument to throw.

The throw keyword can also be used to rethrow the current exception. For example:

image
void g() { throw 2; }
void f()
{
    try {
        g();
    } catch (int i) {
        cout << "caught in f: " << i << endl;
        throw;  // rethrow
    }
}
int main()
{
    try {
        f();
    } catch (int i) {
        cout << "caught in main: " << i << endl;
    }
    return 0;
}

Code snippet from Rethrow ethrow.cpp

This example produces the following output:

caught in f: 2
caught in main: 2

Let’s go back to the readIntegerFile() function. The most likely problem to occur is for the file open to fail. That’s a perfect situation for throwing an exception. This code throws an exception of type exception which requires the <exception> header. The syntax looks like this:

image
#include <exception>
void readIntegerFile(const string& fileName, vector<int>& dest)
{
    ifstream istr;
    int temp;
    istr.open(fileName.c_str());
    if (istr.fail()) {
        // We failed to open the file: throw an exception.
        throw exception();
    }
    // Read the integers one by one and add them to the vector.
    while (istr >> temp) {
        dest.push_back(temp);
    }
}

Code snippet from ReadIntegerFileBasicExceptions.cpp

If the function fails to open the file and executes the throw exception(); line, the rest of the function is skipped, and control transitions to the nearest exception handler.

Throwing exceptions in your code is most useful when you also write code that handles them. Exception handling is a way to “try” a block of code, with another block of code designated to react to any problems that might occur. In the following main() function the catch statement reacts to any exception of type exception that was thrown within the try block by printing an error message. If the try block finishes without throwing an exception, the catch block is skipped. You can think of try/catch blocks as glorified if statements. If an exception is thrown in the try block, execute the catch block. Otherwise, skip it.

image
int main()
{
    vector<int> myInts;
    const string fileName = "IntegerFile.txt";
    try {
        readIntegerFile(fileName, myInts);
    } catch (const exception& e) {
        cerr << "Unable to open file " << fileName << endl;
        return 1;
    }
    for (size_t i = 0; i < myInts.size(); i++) { 
        cout << myInts[i] << " ";
    }
    cout << endl;
    return 0;
}

Code snippet from ReadIntegerFileBasicExceptions.cpp

pen.gif

Although by default, streams do not throw exceptions, you can tell the streams to throw exceptions for error conditions by calling their exceptions( ) method. However, Bjarne Stroustrup, creator of C++, likes to deal with the stream state directly instead of using exceptions (The C++ Programming Language, third edition). This book follows his style in that regard.

Exception Types

You can throw an exception of any type. The preceding example throws an object of type exception, but exceptions do not need to be objects. You could throw a simple int like this:

image
void readIntegerFile(const string& fileName, vector<int>& dest)
{
    // Code omitted
    istr.open(fileName.c_str());
    if (istr.fail()) {
        // We failed to open the file: throw an exception.
        throw 5;
    }
    // Code omitted
}

Code snippet from ReadIntegerFileThrowInt.cpp

You would then need to change the catch statement as well:

image
try {
    readIntegerFile(fileName, myInts);
} catch (int e) {
    cerr << "Unable to open file " << fileName << endl;
    return 1;
}

Code snippet from ReadIntegerFileThrowInt.cpp

Alternatively, you could throw a char* C-style string. This technique is sometimes useful because the string can contain information about the exception.

image
void readIntegerFile(const string& fileName, vector<int>& dest)
{
    // Code omitted
    istr.open(fileName.c_str());
    if (istr.fail()) {
        // We failed to open the file: throw an exception.
        throw "Unable to open file";
    }
    // Code omitted
}

Code snippet from ReadIntegerFileThrowCharStar.cpp

When you catch the char* exception, you can print the result:

image
try {
    readIntegerFile(fileName, myInts);
} catch (const char* e) {
    cerr << e << endl;
    return 1;
}

Code snippet from ReadIntegerFileThrowCharStar.cpp

pen.gif

In modern programming, you should avoid using 8-bit char* C-style strings. You should use Unicode strings which are discussed in detail in Chapter 14.

Despite the previous examples, you should generally throw objects as exceptions for two reasons:

  • Objects convey information by their class name.
  • Objects can store information, including strings that describe the exceptions.

The C++ standard library defines a number of predefined exception classes and you can write your own exception classes. Details are described later in this chapter.

Catching Exception Objects by const and Reference

In the preceding example in which readIntegerFile() throws an object of type exception, the catch line looks like this:

} catch (const exception& e) {

However, there is no requirement to catch objects by const reference. You could catch the object by value like this:

image
} catch (exception e) {

Code snippet from ReadIntegerFileCatchByValue.cpp

Alternatively, you could catch the object by reference (without the const):

image
} catch (exception& e) { 

Code snippet from ReadIntegerFileCatchByNonConstReference.cpp

Also, as you saw in the char* example, you can catch pointers to exceptions, as long as pointers to exceptions are thrown.

pen.gif

It is recommended to catch exceptions by const reference.

Throwing and Catching Multiple Exceptions

Failure to open the file is not the only problem readIntegerFile() could encounter. Reading the data from the file can cause an error if it is formatted incorrectly. Here is an implementation of readIntegerFile() that throws an exception if it cannot either open the file or read the data correctly:

image
void readIntegerFile(const string& fileName, vector<int>& dest)
{
    ifstream istr;
    int temp;
    istr.open(fileName.c_str());
    if (istr.fail()) {
        // We failed to open the file: throw an exception.
        throw exception();
    }
    // 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();
        throw exception();
    }
}

Code snippet from ReadIntegerFileThrowingMultipleBasic.cpp

Your code in main() does not need to change because it already catches an exception of type exception. However, that exception could now be thrown in two different situations, so you should modify the error message accordingly:

image
try {
    readIntegerFile(fileName, myInts);
} catch (const exception& e) {
    cerr << "Unable either to open or to read " << fileName << endl;
    return 1;
}

Code snippet from ReadIntegerFileThrowingMultipleBasic.cpp

Alternatively, you could throw two different types of exceptions from readIntegerFile(), so that the caller can tell which error occurred. Here is an implementation of readIntegerFile() that throws an exception object of class invalid_argument if the file cannot be opened and an object of class runtime_error if the integers cannot be read. Both invalid_argument and runtime_error are classes defined in the header file <stdexcept> as part of the C++ Standard Library.

image
#include <stdexcept>
void readIntegerFile(const string& fileName, vector<int>& dest)
{
    ifstream istr;
    int temp;
    istr.open(fileName.c_str());
    if (istr.fail()) {
        // We failed to open the file: throw an exception.
        throw invalid_argument("");
    }
    // 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();
        throw runtime_error("");
    }
}

Code snippet from ReadIntegerFileThrowingTwoTypes.cpp

There are no public default constructors for invalid_argument and runtime_error, only string constructors.

Now main() can catch both invalid_argument and runtime_error with two catch statements:

image
try {
    readIntegerFile(fileName, myInts);
} catch (const invalid_argument& e) {
    cerr << "Unable to open file " << fileName << endl;
    return 1;
} catch (const runtime_error& e) {
    cerr << "Error reading file " << fileName << endl;
    return 1;
}

Code snippet from ReadIntegerFileThrowingTwoTypes.cpp

If an exception is thrown inside the try block, the compiler will match the type of the exception to the proper catch handler. So, if readIntegerFile() is unable to open the file and throws an invalid_argument object, it will be caught by the first catch statement. If readIntegerFile() is unable to read the file properly and throws a runtime_error, then the second catch statement will catch the exception.

Matching and const

The const-ness specified in the type of the exception you want to catch makes no difference for matching purposes. That is, this line matches any exception of type runtime_error.

} catch (const runtime_error& e) {

The following line also matches any exception of type runtime_error:

} catch (runtime_error& e) {

Matching Any Exception

You can write a catch line that matches any possible exception with the special syntax shown in the following example:

image
try {
    readIntegerFile(fileName, myInts);
} catch (...) {
    cerr << "Error reading or opening file " << fileName << endl;
    return 1;
}

Code snippet from ReadIntegerFileMatchingAnyException.cpp

The three dots are not a typo. They are a wildcard that match any exception type. When you are calling poorly documented code, this technique can be useful to ensure that you catch all possible exceptions. However, in situations where you have complete information about the set of thrown exceptions, this technique is considered suboptimal because it handles every exception type identically. It’s better to match exception types explicitly and take appropriate, targeted action.

A possible use of a catch block matching any exception is as a default catch handler. When an exception is thrown, a catch handler is looked up in the order that they appear in the code. The following example shows how you can write catch handlers that explicitly handle invalid_argument and runtime_error exceptions and how to include a default catch handler for all other exceptions.

try {
    // Code that can throw exceptions
} catch (const invalid_argument& e) {
    // Handle invalid_argument exception
} catch (const runtime_error& e) {
    // Handle runtime_error exception
} catch (...) {
    // Handle all other exceptions
}

Uncaught Exceptions

If your program throws an exception that is not caught anywhere, the program will terminate. Basically there is a try/catch construct around the call to your main() function which catches all unhandled exceptions, something as follows:

try {
    main(argc, argv);
} catch (...) {
    // issue error message and terminate program
}

However, this behavior is not usually what you want. The point of exceptions is to give your program a chance to handle and correct undesirable or unexpected situations.

cross.gif

Catch and handle all possible exceptions thrown in your programs.

Even if you can’t handle a particular exception, you should still write code to catch it and print or show an appropriate error message.

It is also possible to change the behavior of your program if there is an uncaught exception. When the program encounters an uncaught exception, it calls the built-in terminate() function, which calls abort() from <cstdlib> to kill the program. You can set your own terminate_handler by calling set_terminate() with a pointer to a callback function that takes no arguments and returns no value. terminate(), set_terminate(), and terminate_handler are all declared in the <exception> header. The following code shows a high-level overview of how it works.

try {
    main(argc, argv);
} catch (...) {
    if (terminate_handler != nullptr) {
        terminate_handler();
        abort();
    } else {
        terminate();
    }
}
// normal termination code

Before you get too excited about this feature, you should know that your callback function must still terminate the program, or else abort() will be called anyway. It can’t just ignore the error. However, you can use it to print a helpful error message before exiting. Here is an example of a main() function that doesn’t catch the exceptions thrown by readIntegerFile(). Instead, it sets the terminate_handler to a callback that prints an error message before exiting:

image
void myTerminate()
{
    cout << "Uncaught exception!" << endl;
    exit(1);
}
int main()
{
    vector<int> myInts;
    const string fileName = "IntegerFile.txt";
    set_terminate(myTerminate);
    readIntegerFile(fileName, myInts);
    for (size_t i = 0; i < myInts.size(); i++) {
        cout << myInts[i] << " ";
    }
    cout << endl;
    return 0;
}

Code snippet from ReadIntegerFileTerminateHandler.cpp

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:

image
void readIntegerFile(const string& fileName, vector<int>& dest)
    throw(invalid_argument, runtime_error)
{
    // Remainder of the function is the same as before
}

Code snippet from ReadIntegerFileThrowList.cpp

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

image
void readIntegerFile(const string& fileName, vector<int>& dest)
    throw(invalid_argument, runtime_error);

Code snippet from ReadIntegerFileThrowList.cpp

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 use noexcept like this:

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

Code snippet from ReadIntegerFile oexcept.cpp

image

The noexcept keyword is introduced with C++11. If your compiler is not yet C++11 compliant, you should replace the noexcept keyword with throw(). However, it is important to remember that C++11 has deprecated the empty throw() list, which means that it might disappear in future compilers.

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

pen.gif

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

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:

image
void readIntegerFile(const string& fileName, vector<int>& dest)
    throw(invalid_argument, runtime_error)
{
    throw 5;
}

Code snippet from ReadIntegerFileUnexpectedExceptions.cpp

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 which has a catch block for int:

image
int main()
{
    vector<int> myInts;
    const string fileName = "IntegerFile.txt";
    try {
        readIntegerFile(fileName, myInts);
    } catch (int x) {
        cerr << "Caught int" << endl;
    }
    return 0;
}

Code snippet from ReadIntegerFileUnexpectedExceptions.cpp

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

cross.gif

Throw lists don’t prevent functions from throwing unlisted exception types, but they prevent the exception from leaving the function, resulting in a run-time error.

pen.gif

All versions of Microsoft Visual C++, up to version 2010 at the time of this writing, do not yet support throw lists for functions as explained earlier. As a result, the preceding main() function will catch the int exception because Visual C++ just ignores the throw(invalid_argument, runtime_error) specification and will issue a warning like “warning C4290: C++ exception specification ignored except to indicate a function is not __declspec(nothrow)”. Maybe future versions will support this.

You can change this behavior. 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()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 thrown 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:

image
void myUnexpected()
{
    cerr << "Unexpected exception!" << endl;
    throw runtime_error("");
}

Code snippet from ReadIntegerFileUnexpectedExceptions.cpp

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:

image
int main()
{
    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;
        return 1;
    } catch (const runtime_error& e) {
        cerr << "Error reading file " << fileName << endl;
        return 1;
    } catch (int x) {
        cerr << "Caught int" << endl;
    }
    set_unexpected(old_handler);
    // Remainder of function omitted
}

Code snippet from ReadIntegerFileUnexpectedExceptions.cpp

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
  • Making it a noexcept method

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
cross.gif

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:

image
class Base
{
    public:
        virtual void func() throw(exception) { cout << "Base!
"; }
};

Code snippet from ThrowListsVirtualMethodsCorrectOne.cpp

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

image
class Derived : public Base
{
    public:
        virtual void func() noexcept { cout << "Derived!
"; }
};

Code snippet from ThrowListsVirtualMethodsCorrectOne.cpp

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.

image
class Derived : public Base
{
    public:
        virtual void func() throw(exception, runtime_error)
            { cout << "Derived!
"; }
};

Code snippet from ThrowListsVirtualMethodsCorrectTwo.cpp

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:

image
class Base
{
    public:
        virtual void func() throw(runtime_error) { cout << "Base!
"; }
};

Code snippet from ThrowListsVirtualMethodsBroken.cpp

Then you cannot override func() in Derived with a throw list like this:

image
class Derived : public Base
{
    public:
        virtual void func() throw(exception) // ERROR!
            { cout << "Derived!
"; }
};

Code snippet from ThrowListsVirtualMethodsBroken.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:

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:

image
void readIntegerFile(const string& fileName, vector<int>& dest)
    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 ExceptionsAndPolymorphismUsingWhat.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 and runtime_error are both subclasses of exception, so you can replace the two catch statements with a single catch statement for class exception:

image
try {
    readIntegerFile(fileName, myInts);
} catch (const exception& e) {
    cerr << e.what() << endl;
    return 1;
}

Code snippet from ExceptionsAndPolymorphismCatchingPolymorphicallyCorrectOne.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.

cross.gif

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:

image
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 ExceptionsAndPolymorphismCatchingPolymorphicallyCorrectTwo.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:

image
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 ExceptionsAndPolymorphismCatchingPolymorphicallyIncorrect.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:

image
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 ExceptionsAndPolymorphismWritingExceptions.cpp

As a good programming citizen, you should make FileError a part of the standard exception hierarchy. It seems appropriate to integrate it as a child of runtime_error. When you write a subclass of runtime_error (or any other exception in the standard hierarchy), you need to override the what() method, which has the prototype shown and is supposed to return a const char* string that is valid until the object is destroyed. In the case of FileError, this string comes from the mMsg data member, which is set to "" in the constructor. Subclasses of FileError must set this mMsg string to something different if they want a different message.

The generic FileError class also contains a filename and an accessor for that filename.

The first exceptional situation in readIntegerFile() occurs if the file cannot be opened. Thus, you might want to write a FileOpenError subclass of FileError:

image
class FileOpenError : public FileError
{
    public:
        FileOpenError(const string& fileNameIn);
};
FileOpenError::FileOpenError(const string& fileNameIn) : FileError(fileNameIn)
{
    mMsg = "Unable to open " + fileNameIn;
}

Code snippet from ExceptionsAndPolymorphismWritingExceptions.cpp

The FileOpenError changes the mMsg string to represent the file-opening error.

The second exceptional situation in readIntegerFile() occurs if the file cannot be read properly. It might be useful for this exception to contain the line number of the error in the file, as well as the filename and the error message string returned from what(). Here is a FileReadError subclass of FileError:

image
class FileReadError : public FileError
{
    public:
        FileReadError(const string& fileNameIn, int lineNumIn);
        int getLineNum() { return mLineNum; }
    protected:
        int mLineNum;
};
FileReadError::FileReadError(const string& fileNameIn, int lineNumIn)
    : FileError(fileNameIn), mLineNum(lineNumIn)
{
    ostringstream ostr;
    ostr << "Error reading " << fileNameIn << " at line " << lineNumIn;
    mMsg = ostr.str();
}

Code snippet from ExceptionsAndPolymorphismWritingExceptions.cpp

Of course, in order to set the line number properly, you need to modify your readIntegerFile() function to track the number of lines read instead of just reading integers directly. Here is a new readIntegerFile() function that uses the new exceptions:

image
void readIntegerFile(const string& fileName, vector<int>& dest)
    throw(FileOpenError, FileReadError)
{
    ifstream istr;
    int temp;
    string line;
    int lineNumber = 0;
    istr.open(fileName.c_str());
    if (istr.fail()) {
        // We failed to open the file: throw an exception.
        throw FileOpenError(fileName);
    }
    while (!istr.eof()) {
        // Read one line from the file.
        getline(istr, line);
        lineNumber++;
        // Create a string stream out of the line.
        istringstream lineStream(line);
        // Read the integers one by one and add them to the vector.
        while (lineStream >> temp) {
            dest.push_back(temp);
        }
        if (!lineStream.eof()) {
            // Some other error. Close the file and throw an exception.
            istr.close();
            throw FileReadError(fileName, lineNumber);
        }
    }
    istr.close();
}

Code snippet from ExceptionsAndPolymorphismWritingExceptions.cpp

Now, code that calls readIntegerFile() can use polymorphism to catch exceptions of type FileError like this:

image
try {
    readIntegerFile(fileName, myInts);
} catch (const FileError& e) {
    cerr << e.what() << endl;
    return 1;
}

Code snippet from ExceptionsAndPolymorphismWritingExceptions.cpp

There is one trick to writing classes whose objects will be used as exceptions. When a piece of code throws an exception, the object or value thrown is copied. That is, a new object is constructed from the old object by using the copy constructor. It must be copied because the original could go out of scope (and be destroyed and have its memory reclaimed) before the exception is caught, higher up in the stack. Thus, if you write a class whose objects will be thrown as exceptions, you must make those objects copyable. This means that if you have dynamically allocated memory, you must write a destructor, copy constructor, and assignment operator, as described in Chapter 7.

cross.gif

Objects thrown as exceptions are always copied by value at least once.

It is possible for exceptions to be copied more than once, but only if you catch the exception by value instead of by reference.

pen.gif

Catch exception objects by reference to avoid unnecessary copying.

imageNested Exceptions

It could happen that during handling of a first exception, a second exceptional situation is triggered which requires a second exception to be thrown. Unfortunately, when you throw the second exception, all information about the first exception that you are currently trying to handle will be lost. C++11 provides a solution to this problem with the concept of nested exceptions, which allow you to nest a caught exception in the context of a new exception. To that end, a mix-in class called std::nested_exception is provided which captures and stores a copy of the exception currently being processed. A catch handler for the second exception can use a dynamic_cast to get access to the nested_exception representing the first exception. The following example will demonstrate the use of nested exceptions. The first thing you need to do is to define your own exception class and at least inherit from the nested_exception mix-in class. This example defines a MyException class which derives from exception and from the mix-in class nested_exception. It also accepts a string in its constructor.

image
class MyException : public std::exception, public std::nested_exception
{
    public:
        MyException(const char* msg) : mMsg(msg) {}
        virtual ~MyException() noexcept {}
        virtual const char* what() const noexcept { return mMsg.c_str(); }
    protected:
        std::string mMsg;
};

Code snippet from NestedExceptionNestedException.cpp

When you are handling a first exception and you need to throw a second exception with the first one nested inside it, you need to use the std::throw_with_nested() function. This function requires as parameter an instance of a class that inherits from nested_exception, like the MyException class in this example. The following doSomething() function throws a runtime_error which is immediately caught in the catch handler. The catch handler writes a message and then uses the throw_with_nested() function to throw a second exception that has the first one nested inside it. Note that nesting the exception inside the MyException instance happens automatically due to the nested_exception mix-in class.

image
void doSomething()
{
    try {
        throw std::runtime_error("Throwing a runtime_error exception");
    } catch (const std::runtime_error& e) {
        std::cout << __func__ << " caught a runtime_error" << std::endl;
        std::cout << __func__ << " throwing MyException" << std::endl;
        std::throw_with_nested(
            MyException("MyException with nested runtime_error"));
    }
}

Code snippet from NestedExceptionNestedException.cpp

The following main() function demonstrates how to handle the exception with a nested exception. The code calls the doSomething() function and has one catch handler for exceptions of type MyException. When it catches such an exception, it writes a message and then uses a dynamic_cast to get access to the nested exception. If there is no nested exception inside, the result will be a null pointer. If there is a nested exception inside, the rethrow_nested() method on the nested_exception is called. This will cause the nested exception to be rethrown which you can then catch in another try/catch block.

image
int main()
{
    try {
        doSomething();
    } catch (const MyException& e) {
        std::cout << __func__ << " caught MyException: " << e.what()
                  << std::endl;
        const std::nested_exception* pNested =
            dynamic_cast<const std::nested_exception*>(&e);
        if (pNested) {
            try {
                pNested->rethrow_nested();
            } catch (const std::runtime_error& e) {
                // Handle nested exception
                std::cout << "  Nested exception: " << e.what()
                          << std::endl;
            }
        }
    }
    return 0;
}

Code snippet from NestedExceptionNestedException.cpp

The output should be as follows:

doSomething caught a runtime_error
doSomething throwing MyException
main caught MyException: MyException with nested runtime_error
  Nested exception: Throwing a runtime_error exception

The preceding main() function uses a dynamic_cast to check for the nested exception. Since you always have to perform this dynamic_cast if you want to check for a nested exception, the standard provides a small wrapper called std::rethrow_if_nested() that does it for you. This wrapper can be used as follows:

image
int main()
{
    try {
        doSomething();
    } catch (const MyException& e) {
        std::cout << __func__ << " caught MyException: " << e.what()
                  << std::endl;
        try {
            std::rethrow_if_nested(e);
        } catch (const std::runtime_error& e) {
            // Handle nested exception
            std::cout << "  Nested exception: " << e.what() << std::endl;
        }
    }
    return 0;
}

Code snippet from NestedExceptionNestedException.cpp

STACK UNWINDING AND CLEANUP

When a piece of code throws an exception, it searches for a catch handler on the stack. This catch handler could be zero or more function calls up the stack of execution. When one is found, the stack is stripped back to the stack level that defines the catch handler by unwinding all intermediate stack frames. Stack unwinding means that the destructors for all locally-scoped names are called and all code remaining in each function past the current point of execution is skipped.

However, in stack unwinding, pointer variables are not freed, and other cleanup is not performed. This behavior can present problems, as the following code demonstrates:

image
void funcOne() throw(exception);
void funcTwo() throw(exception);
int main()
{
    try {
        funcOne();
    } catch (const exception& e) {
        cerr << "Exception caught!" << endl;
        return 1;
    }
    return 0;
}
void funcOne() throw(exception)
{
    string str1;
    string* str2 = new string();
    funcTwo();
    delete str2;
}
void funcTwo() throw(exception)
{
    ifstream istr;
    istr.open("filename");
    throw exception();
    istr.close();
}

Code snippet from StackUnwindingBadCode.cpp

When funcTwo() throws an exception, the closest exception handler is in main(). Control then jumps immediately from this line in funcTwo():

throw exception();

to this line in main():

cerr << "Exception caught!" << endl;

In funcTwo(), control remains at the line that threw the exception, so this subsequent line never gets a chance to run:

istr.close();

However, luckily for you, the ifstream destructor is called because istr is a local variable on the stack. The ifstream destructor closes the file for you, so there is no resource leak here. If you had dynamically allocated istr, it would not be destroyed, and the file would not be closed.

In funcOne(), control is at the call to funcTwo(), so this subsequent line never gets a chance to run:

delete str2;

In this case, there really is a memory leak. Stack unwinding does not automatically call delete on str2 for you. However, str1 is destroyed properly because it is a local variable on the stack. Stack unwinding destroys all local variables correctly.

cross.gif

Careless exception handling can lead to memory and resource leaks.

This is one reason you should never mix older C models of allocation (even if you are calling new so it looks like C++) with modern programming methodologies like exceptions. In C++, this situation should be handled by one of the techniques discussed in the following two sections.

Use Smart Pointers

The first and recommended technique is to use smart pointers. They allow you to write code that automatically prevents memory or resource leaks with exception handling. Chapter 21 discusses smart pointers in detail. Smart pointer objects are allocated on the stack and whenever the smart pointer object is destroyed, it frees the underlying resource. Here is an example of the previous funcOne() function but using the unique_ptr (C++11) smart pointer:

image
#include <memory>
using namespace std;
void funcOne() throw(exception)
{
    string str1;
    unique_ptr<string> str2(new string("hello"));
    funcTwo();
}

Code snippet from StackUnwindingSmartPointer.cpp

The str2 pointer of type string* will automatically be deleted when you return from funcOne() or when an exception is thrown.

pen.gif

With smart pointers, you never have to remember to free the underlying resource: the smart pointer destructor does it for you, whether you leave the function via an exception or leave the function normally.

Catch, Cleanup, and Rethrow

The next technique for avoiding memory and resource leaks is for each function to catch any possible exceptions, perform necessary cleanup work, and rethrow the exception for the function higher up the stack to handle. Here is a revised funcOne() with this technique:

image
void funcOne() throw(exception)
{
    string str1;
    string* str2 = new string();
    try {
        funcTwo();
    } catch (...) {
        delete str2;
        throw; // Rethrow the exception.
    }
    delete str2;
}

Code snippet from StackUnwindingCatchAndRethrow.cpp

This function wraps the call to funcTwo() with an exception handler that performs the cleanup (calls delete on str2) and then rethrows the exception. The keyword throw by itself rethrows whatever exception was caught most recently. Note that the catch statement uses the ... syntax to catch any exception.

This method works fine, but can be messy. In particular, note that there are now two identical lines that call delete on str2: one to handle the exception and one if the function exits normally.

cross.gif

The preferred solution is to use smart pointers instead of the catch, cleanup, and rethrow technique.

COMMON ERROR-HANDLING ISSUES

Whether or not you use exceptions in your programs is up to you and your colleagues. However, we strongly encourage you to formalize an error-handling plan for your programs, regardless of your use of exceptions. If you use exceptions, it is generally easier to come up with a unified error-handling scheme, but it is not impossible without exceptions. The most important aspect of a good plan is uniformity of error handling throughout all the modules of the program. Make sure that every programmer on the project understands and follows the error-handling rules.

This section discusses the most common error-handling issues in the context of exceptions, but the issues are also relevant to programs that do not use exceptions.

Memory Allocation Errors

Despite the fact that all of our examples so far in this book have ignored the possibility, memory allocation can, and will, fail. However, production code must account for memory allocation failures. C++ provides several different ways to handle memory errors.

The default behaviors of new and new[] are to throw an exception of type bad_alloc, defined in the <new> header file, if they cannot allocate memory. Your code should catch these exceptions and handle them appropriately.

Thus, all your new statements should look something like this:

image
try {
    ptr = new int[numInts];
} catch (const bad_alloc& e) {
    cerr << __FILE__ << "(" << __LINE__
         << "): Unable to allocate memory!" << endl;
    // Handle memory allocation failure.
    return;
}
// Proceed with function that assumes memory has been allocated.

Code snippet from NewFailuresExceptions.cpp

Note that this code uses the predefined preprocessor symbols __FILE__ and __LINE__ which will be replaced with the name of the file and the current line number. This makes debugging easier.

pen.gif

This example prints an error message to cerr. This assumes your program is running with a console. In GUI applications, you often don’t have a console in which case you need to show the error in a GUI specific way to the user.

You could, of course, bulk handle many possible new failures with a single try/catch block at a higher point in the program, if it will work for your program.

Another consideration is that logging an error might try to allocate memory. If new fails, there might not be enough memory left even to log the error message.

Non-Throwing new

As Chapter 21 explains, if you don’t like exceptions, you can revert to the old C model in which memory allocation routines return a null pointer if they cannot allocate memory. C++ provides nothrow versions of new and new[], which return nullptr instead of throwing an exception if they fail to allocate memory. This is done by using the syntax new(nothrow) instead of new as shown in the following example.

image
ptr = new(nothrow) int[numInts];
if (ptr == nullptr) {
    cerr << __FILE__ << "(" << __LINE__
         << "): Unable to allocate memory!" << endl;
    // Handle memory allocation failure.
    return;
}
// Proceed with function that assumes memory has been allocated.

Code snippet from NewFailuresNothrow.cpp

The syntax is a little strange: you really do write “nothrow” as if it’s an argument to new (which it is).

Customizing Memory Allocation Failure Behavior

C++ allows you to specify a new handler callback function. By default, there is no new handler, so new and new[] just throw bad_alloc exceptions. However, if there is a new handler, the memory allocation routine calls the new handler upon memory allocation failure instead of throwing an exception. If the new handler returns, the memory allocation routines attempt to allocate memory again, calling the new handler again if they fail. This cycle could become an infinite loop unless your new handler changes the situation with one of four alternatives. Practically speaking, some of the four options are better than others. Here is the list with commentary:

  • Make more memory available. One trick to expose space is to allocate a large chunk of memory at program start-up, and then to free it in the new handler. A practical example is when you hit an allocation error and you need to save the user state so no work gets lost. The key is to allocate a block of memory at program start-up large enough to allow a complete document save operation. When the new handler is triggered, you free this block, save the document, restart the application and let it reload the saved document.
  • Throw an exception. new and new[] have throw lists that say they will throw exceptions only of type bad_alloc. So, unless you want to create a call to unexpected(), if you throw an exception from the new handler, throw bad_alloc or a subclass. For example, when your new handler is triggered, you can throw a document_recovery_alloc exception which inherits from bad_alloc. You can catch this exception somewhere in your application and trigger the document save operation and restart of the application.
  • Set a different new handler. Theoretically, you could have a series of new handlers, each of which tries to create memory and sets a different new handler if it fails. However, such a scenario is usually more complicated than useful.
  • Terminate the program. Your new handler can log an error message and throw an agreed-upon exception such as PleaseTerminateMe. In your top-level function, for example main(), you catch this exception and handle it by returning from the top-level function. Never explicitly terminate the program by using exit() or abort(), only by returning from the top-level function. If there are some memory allocations that can fail but still allow your program to succeed, you can simply set the new handler back to its default of nullptr temporarily before calling new in those cases.

If you don’t do one of these four things in your new handler, any memory allocation failure will cause an infinite loop.

You set the new handler with a call to set_new_handler(), declared in the <new> header file. set_new_handler() completes the trio of C++ functions to set callback functions. The other two are set_terminate() and set_unexpected(), which were discussed earlier in this chapter. Here is an example of a new handler that logs an error message and throws an exception:

image
class PleaseTerminateMe { };
void myNewHandler()
{
    cerr << __FILE__ << "(" << __LINE__
         << "): Unable to allocate memory." << endl;
    throw PleaseTerminateMe();
}

Code snippet from NewFailuresNewHandler.cpp

The new handler must take no arguments and return no value. This new handler throws a PleaseTerminateMe exception like suggested in the fourth bullet in the preceding list.

You can set the new handler like this:

image
int main()
{
    try {
        // Set the new new_handler and save the old.
        new_handler oldHandler = set_new_handler(myNewHandler);
        // Generate allocation error
        int numInts = numeric_limits<int>::max();
        int* ptr = new int[numInts];
        // reset the old new_handler
        set_new_handler(oldHandler);
    } catch (const PleaseTerminateMe&) {
        cerr << __FILE__ << "(" << __LINE__
             << "): Terminating program." << endl;
        return 1;
    }
    return 0;
}

Code snippet from NewFailuresNewHandler.cpp

Note that new_handler is a typedef for the type of function pointer that set_new_handler() takes.

Errors in Constructors

Before C++ programmers discover exceptions, they are often stymied by error handling and constructors. What if a constructor fails to construct the object properly? Constructors don’t have a return value, so the standard pre-exception error-handling mechanism doesn’t work. Without exceptions, the best you can do is to set a flag in the object specifying that it is not constructed properly. You can provide a method, with a name like checkConstructionStatus(), which returns the value of that flag, and hope that clients remember to call the function on the object after constructing it.

Exceptions provide a much better solution. You can throw an exception from a constructor, even though you can’t return a value. With exceptions you can easily tell clients whether or not construction of the object succeeded. However, there is one major problem: if an exception leaves a constructor, the destructor for that object will never be called. Thus, you must be careful to clean up any resources and free any allocated memory in constructors before allowing exceptions to leave the constructor. This problem is the same as in any other function, but it is subtler in constructors because you’re accustomed to letting the destructors take care of the memory deallocation and resource freeing.

This section describes a Matrix class as an example in which the constructor correctly handles exceptions. The definition of the Matrix class looks as follows:

image
#include <stdexcept>
#include "Element.h"
class Matrix
{
    public:
        Matrix(unsigned int width, unsigned int height) throw(std::bad_alloc);
        virtual ~Matrix();
    protected:
        unsigned int mWidth;
        unsigned int mHeight;
        Element** mMatrix;
};

Code snippet from ConstructorErrorMatrix.h

The preceding class uses the Element class, which is kept at a bare minimum for this example:

image
class Element
{
    protected:
        int mValue;
};

Code snippet from ConstructorErrorElement.h

The implementation of the Matrix class is as follows. Note that the first call to new is not protected with a try/catch block. It doesn’t matter if the first new throws an exception because the constructor hasn’t allocated anything else yet that needs freeing. If any of the subsequent new calls throw exceptions, though, the constructor must clean up all of the memory already allocated. However, it doesn’t know what exceptions the Element constructors themselves might throw, so it catches any exception via ... and translates them into a bad_alloc exception. It is also important to have index i outside the try block because this index is needed during cleanup in the catch block.

image
Matrix::Matrix(unsigned int width, unsigned int height) throw(bad_alloc)
    : mWidth(width), mHeight(height), mMatrix(nullptr)
{
    mMatrix = new Element*[width];
    unsigned int i = 0;
    try {
        for (i = 0; i < height; ++i)
            mMatrix[i] = new Element[height];
    } catch (...) {
        cout << "Exception caught in constructor, cleaning up..." << endl;
        // Clean up any memory we already allocated, because the destructor
        // will never be called. The upper bound of the for loop is the
        // index of the last element in the mMatrix array that we tried
        // to allocate (the one that failed). All indices before that
        // one store pointers to allocated memory that must be freed.
        for (unsigned int j = 0; j < i; j++) {
          delete [] mMatrix[j];
        }
        delete [] mMatrix;
        mMatrix = nullptr;
        // Translate any exception to bad_alloc.
        throw bad_alloc();
    }
}
Matrix::~Matrix()
{
    for (unsigned int i = 0; i < mHeight; ++i)
        delete [] mMatrix[i];
    delete [] mMatrix;
    mMatrix = nullptr;
}

Code snippet from ConstructorErrorMatrix.cpp

cross.gif

Remember, if an exception leaves a constructor, the destructor for that object will never be called!

You might be wondering what happens when you add inheritance into the mix. Superclass constructors run before subclass constructors. If a subclass constructor throws an exception, how are the resources that the superclass constructor allocated freed?

pen.gif

C++ guarantees that it will run the destructor for any fully constructed “subobjects.” Therefore, any constructor that completes without an exception will cause the corresponding destructor to be run.

Function-Try-Blocks for Constructors

The exception mechanism as discussed up to now in this chapter is perfect to handle exceptions within functions. However, how should you handle exceptions thrown from inside a ctor-initializer of a constructor? This section explains a feature called function-try-blocks, which are capable of catching those exceptions. Most C++ programmers, even experienced C++ programmers don’t know the existence of this feature, even though it was introduced more than a decade ago.

The following piece of pseudo code shows the basic syntax for a function-try-block for a constructor:

MyClass::MyClass()
try
    : <ctor-initializer>
{
    /* ... constructor body ... */
}
catch (const exception& e)
{
    /* ... */
}

As you can see, the try keyword should be right before the start of the ctor-initializer. The catch statements should be after the closing brace for the constructor, actually putting them outside the constructor body. There are a number of restrictions and guidelines that you should keep in mind when using function-try-blocks with constructors:

  • The catch statements will catch any exception either thrown directly or indirectly by the ctor-initializer or by the constructor body.
  • The catch statements have to rethrow the current exception or throw a new exception. If a catch statement doesn’t do this, the run time will automatically rethrow the current exception.
  • When a catch statement catches an exception in a function-try-block, all objects that have already been constructed by the constructor will be destroyed before execution of the catch statement starts.
  • You should not access member variables for the object inside a function-try-block catch statement.
  • The catch statements in a function-try-block cannot use the return keyword to return a value from the function enclosed by it. This is not relevant for constructors because they do not return anything.

Based on this list of limitations, function-try-blocks for constructors are useful only in a very limited number of situations:

  • To convert an exception thrown by the ctor-initializer to another exception.
  • To log a message to a log file.

Let’s see how to use function-try-blocks with an example. The following code defines a class called SubObject. It has only one constructor, which throws an exception of type runtime_error.

image
class SubObject
{
    public:
        SubObject(int i) throw(std::runtime_error);
};
SubObject::SubObject(int i) throw(std::runtime_error)
{
    throw std::runtime_error("Exception by SubObject ctor");
}

Code snippet from FunctionTryBlockFunctionTryBlocks.cpp

The MyClass class has a member variable of type SubObject:

image
class MyClass
{
    public:
        MyClass() throw(std::runtime_error);
    protected:
        SubObject mSubObject;
};

Code snippet from FunctionTryBlockFunctionTryBlocks.cpp

The SubObject class does not have a default constructor. This means that you need to initialize mSubObject in the MyClass ctor-initializer. The constructor of the MyClass class will use a function-try-block to catch exceptions thrown in its ctor-initializer:

image
MyClass::MyClass() throw(std::runtime_error)
try
    : mSubObject(42)
{
    /* ... constructor body ... */
}
catch (const std::exception& e)
{
    cout << "function-try-block caught: '" << e.what() << "'" << endl;
}

Code snippet from FunctionTryBlockFunctionTryBlocks.cpp

Remember that catch statements in a function-try-block for a constructor have to either rethrow the current exception or throw a new exception. The preceding catch statement does not throw anything, so the C++ run time will automatically rethrow the current exception. Following is a simple function that uses the preceding class:

image
int main()
{
    try {
        MyClass m;
    } catch (const std::exception& e) {
        cout << "main() caught: '" << e.what() << "'" << endl;
    }
    return 0;
}

Code snippet from FunctionTryBlockFunctionTryBlocks.cpp

The output of the preceding example is as follows:

function-try-block caught: 'Exception by SubObject ctor'
main() caught: 'Exception by SubObject ctor'

Function-try-blocks are not limited to constructors. They can be used with ordinary functions as well. However, for normal functions, there is no useful reason to use function-try-blocks because they can just as easily be converted to a simple try/catch block inside the function body. One notable difference when using a function-try-block on a normal function compared to a constructor is that rethrowing the current exception or throwing a new exception in the catch statements is not required and the C++ run time will not automatically rethrow the exception.

Errors in Destructors

You should handle all error conditions that arise in destructors in the destructors themselves. You should not let any exceptions be thrown from destructors, for three reasons:

1. Destructors can run while there is another pending exception, in the process of stack unwinding. If you throw an exception from the destructor in the middle of stack unwinding, the program will terminate. For the brave and curious, C++ does provide the ability to determine, in a destructor, whether you are executing as a result of a normal function exit or delete call, or because of stack unwinding. The function uncaught_exception(), declared in the <exception> header file, returns true if there is an uncaught exception and you are in the middle of stack unwinding. Otherwise, it returns false. However, this approach is messy and should be avoided.

2. What action would clients take? Clients don’t call destructors explicitly: they call delete, which calls the destructor. If you throw an exception from the destructor, what is a client supposed to do? It can’t call delete on the object again, and it shouldn’t call the destructor explicitly. There is no reasonable action the client can take, so there is no reason to burden that code with exception handling.

3. The destructor is your one chance to free memory and resources used in the object. If you waste your chance by exiting the function early due to an exception, you will never be able to go back and free the memory or resources.

Therefore, be careful to catch in a destructor any exceptions that can be thrown by calls you make from the destructor. Normally, destructors call only delete and delete[], which cannot throw exceptions, so there should be no problem.

PUTTING IT ALL TOGETHER

Now that you’ve learned about error handling and exceptions, let’s see it all coming together in a bigger example, a GameBoard class. This GameBoard class will come back in Chapter 19. First, here is the definition of the class without any exceptions.

image
class GameBoard
{
    public:
        // general-purpose GameBoard allows user to specify its dimensions
        GameBoard(int inWidth = kDefaultWidth,
            int inHeight = kDefaultHeight);
        GameBoard(const GameBoard& src); // Copy constructor
        virtual ~GameBoard();
        GameBoard& operator=(const GameBoard& rhs); // Assignment operator
        void setPieceAt(int x, int y, const GamePiece& inPiece);
        GamePiece& getPieceAt(int x, int y);
        const GamePiece& getPieceAt(int x, int y) const;
        int getHeight() const { return mHeight; }
        int getWidth() const { return mWidth; }
        static const int kDefaultWidth = 100;
        static const int kDefaultHeight = 100;
    protected:
        void copyFrom(const GameBoard& src);
        // Objects dynamically allocate space for the game pieces.
        GamePiece** mCells;
        int mWidth, mHeight;
};

Code snippet from GameBoardNoExceptionsGameBoard.h

And here is the implementation without any exceptions:

image
GameBoard::GameBoard(int inWidth, int inHeight) :
    mWidth(inWidth), mHeight(inHeight)
{
    mCells = new GamePiece* [mWidth];
    for (int i = 0; i < mWidth; i++) {
        mCells[i] = new GamePiece[mHeight];
    }
}
GameBoard::GameBoard(const GameBoard& src)
{
    copyFrom(src);
}
GameBoard::~GameBoard()
{
    // Free the old memory
    for (int i = 0; i < mWidth; i++) {
        delete [] mCells[i];
    }
    delete [] mCells;
    mCells = nullptr;
}
void GameBoard::copyFrom(const GameBoard& src)
{
    mWidth = src.mWidth;
    mHeight = src.mHeight;
    mCells = new GamePiece* [mWidth];
    for (int i = 0; i < mWidth; i++) {
        mCells[i] = new GamePiece[mHeight];
    }
    for (int i = 0; i < mWidth; i++) {
        for (int j = 0; j < mHeight; j++) {
            mCells[i][j] = src.mCells[i][j];
        }
    }
}
GameBoard& GameBoard::operator=(const GameBoard& rhs)
{
    // Check for self-assignment
    if (this == &rhs) {
        return *this;
    }
    // Free the old memory
    for (int i = 0; i < mWidth; i++) {
        delete [] mCells[i];
    }
    delete [] mCells;
    mCells = nullptr;
    // Copy the new memory
    copyFrom(rhs);
    return *this;
}
void GameBoard::setPieceAt(int x, int y, const GamePiece& inElem)
{
    mCells[x][y] = inElem;
}
GamePiece& GameBoard::getPieceAt(int x, int y)
{
    return mCells[x][y];
}
const GamePiece& GameBoard::getPieceAt(int x, int y) const
{
    return mCells[x][y];
}

Code snippet from GameBoardNoExceptionsGameBoard.cpp

Now, let’s retrofit the preceding class to include error handling and exceptions. The constructors, operator= and copyFrom() can all throw bad_alloc because they perform memory allocation. The destructor, getHeight(), and getWidth() throw no exceptions. setPieceAt() and getPieceAt() throw out_of_range if the caller supplies an invalid coordinate. Here is the retrofitted class definition:

image
#include <stdexcept>
#include <new>
using std::bad_alloc;
using std::out_of_range;
class GameBoard
{
    public:
        GameBoard(int inWidth = kDefaultWidth,
            int inHeight = kDefaultHeight) throw(bad_alloc);
        GameBoard(const GameBoard& src) throw(bad_alloc);
        virtual ~GameBoard() noexcept;
        GameBoard& operator=(const GameBoard& rhs) throw(bad_alloc);
        void setPieceAt(int x, int y, const GamePiece& inPiece)
            throw(out_of_range);
        GamePiece& getPieceAt(int x, int y) throw(out_of_range);
        const GamePiece& getPieceAt(int x, int y) const
            throw(out_of_range);
        int getHeight() const noexcept { return mHeight; }
        int getWidth() const noexcept { return mWidth; }
        static const int kDefaultWidth = 100;
        static const int kDefaultHeight = 100;
    protected:
        void copyFrom(const GameBoard& src) throw(bad_alloc);
        GamePiece** mCells;
        int mWidth, mHeight;
};

Code snippet from GameBoardGameBoard.h

Here are the implementations of the constructor, copyFrom(), and setPieceAt() methods with exception handling. getPieceAt() is not shown because it is similar to setPieceAt().The implementations of the copy constructor and operator= did not change except for their throw lists because all the work is in copyFrom(), so their implementations are not shown. The destructor also did not change, so its implementation is not shown either.

image
GameBoard::GameBoard(int inWidth, int inHeight) throw(bad_alloc) :
    mWidth(inWidth), mHeight(inHeight)
{
    int i = 0;
    mCells = new GamePiece* [mWidth];
    try {
        for (i = 0; i < mWidth; i++) {
            mCells[i] = new GamePiece[mHeight];
        }
    } catch (...) {
        // Cleanup any memory we already allocated, because the destructor
        // will never get called. The upper bound of the for loop is the
        // index of the last element in the mCells array that we tried
        // to allocate (the one that failed). All indices before that
        // one store pointers to allocated memory that must be freed.
        for (int j = 0; j < i; j++) {
          delete [] mCells[j];
        }
        delete [] mCells;
        mCells = nullptr;
        // Translate any exception to bad_alloc.
        throw bad_alloc();
    }
}
void GameBoard::copyFrom(const GameBoard& src) throw(bad_alloc)
{
    int i = 0;
    mWidth = src.mWidth;
    mHeight = src.mHeight;
    mCells = new GamePiece *[mWidth];
    try {
        for (i = 0; i < mWidth; i++) {
            mCells[i] = new GamePiece[mHeight];
        }
    } catch (...) {
        // Clean up any memory we already allocated.
        // If this function is called from the copy constructor,
        // the destructor will never be called.
        // Use the same loop upper bound as described in the constructor.
        for (int j = 0; j < i; j++) {
            delete [] mCells[j];
        }
        delete [] mCells;
        // Set mCells and mWidth to values that will allow the
        // destructor to run without harming anything.
        // This function is called from operator=, in which case the
        // object was already constructed, so the destructor will be
        // called at some point.
        mCells = nullptr;
        mWidth = 0;
        throw bad_alloc();
    }
    for (i = 0; i < mWidth; i++) {
        for (int j = 0; j < mHeight; j++) {
            mCells[i][j] = src.mCells[i][j];
        }
    }
}
void GameBoard::setPieceAt(int x, int y, const GamePiece& inElem)
    throw(out_of_range)
{
    // Check for out of range arguments
    if (x < 0)
        throw out_of_range("GameBoard::setPieceAt: x-coord negative");
    if (x >= mWidth)
        throw out_of_range("GameBoard::setPieceAt: x-coord beyond width");
    if (y < 0)
        throw out_of_range("GameBoard::setPieceAt: y-coord negative");
    if (y >= mHeight)
        throw out_of_range("GameBoard::setPieceAt: y-coord beyond height");
 
    mCells[x][y] = inElem;
}

Code snippet from GameBoardGameBoard.cpp

SUMMARY

This chapter described the issues related to error handling in C++ programs, and emphasized that you must design and code your programs with an error-handling plan. By reading this chapter, you learned the details of C++ exceptions syntax and behavior. The chapter also covered some of the areas in which error handling plays a large role, including I/O streams, memory allocation, constructors, and destructors. Finally, you saw an example of error handling in a GameBoard class.

Classes and functionality of the C++ standard library have already been used extensively throughout this book, so it’s time to go deeper in on that subject. The next few chapters start delving into the C++ standard library.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset