course of C++ programming language

lecture 11: exception handling


Exception handling fundamentals

When a program is composed of separate modules, and especially when those modules come from separately developed libraries, error handling needs to be separated into two distinct parts:
[1] The reporting of error conditions that cannot be resolved locally.
[2] The handling of errors detected elsewhere.

The author of a library can detect run-time errors but does not in general have any idea what to do about them. The user of a library may know how to cope with such errors but cannot detect them - or else they would have been handled in the user's code and not left for the library to find. The notion of an exception is provided to help deal with such problems. The fundamental idea is that a function that finds a problem it cannot cope with throws an exception, hoping that its (direct or indirect) caller can handle the problem. A function that wants to handle that kind of problem can indicate that it is willing to catch that exception.

double sqrt_of_delta (double a, double b, double c) { if (a==0) throw string("a = 0"); double d = b*b-4*a*c; if (d<0) throw string("delta < 0"); return sqrt(d); } int main () { double a, b, c; cin >> a >> b >> c; double d; try { d = sqrt_of_delta(a,b,c); // an exception may be thrown if (d==0) cout << (-b/(2*a)) << endl; else cout << ((-b-d)/(2*a)) << endl << ((-b+d)/(2*a)) << endl; } catch (string ex) { cerr << "an error occurs" << endl; } }

C++ exception handling is built upon three keywords: try, catch, and throw. In the most general terms, program statements that you want to monitor for exceptions are contained in a try block. If an exception occurs within the try block, it is thrown (using throw statements). The exception is caught, using catch, and processed.

Code that you want to monitor for exceptions must have been executed from within a try block. Functions called from within a try block may also throw an exception. Exceptions that can be thrown by the monitored code are caught by a catch statement, which immediately follows the try statement in which the exception was thrown.

try { // try block } catch (type1 arg) { // catch block } catch (type2 arg) { // catch block } // ... catch (typeN arg) { // catch block }

When an exception is thrown, it is caught by its corresponding catch statement, which processes the exception. There can be more than one catch statement associated with a try. Which catch statement is used is determined by the type of the exception. That is, if the data type specified by a catch matches that of the exception, then that catch statement is executed. When an exception is caught, arg will receive its value. Any type of data may be caught, including classes that you create. If no exception is thrown (that is, no error occurs within the try block), then no catch statement is executed.

throw exception;

The throw statement generates the exception specified by exception. If this exception is to be caught, then throw must be executed either from within a try block itself, or from any function called from within the try block (directly or indirectly).

Catching exceptions

An exception can be of any type, including class types that you create. Actually, in real-world programs, most exceptions will be class types rather than built-in types. Perhaps the most common reason that you will want to define a class type for an exception is to create an object that describes the error that occurred. This information can be used by the exception handler to help it process the error.

#include <iostream> #include <string> using namespace std; class MyException { public: const string str_what; const int what; MyException () : str_what(""), what(0) {} MyException (const string &s, int e) : str_what(s), what(e) {} }; int main() { int i; try { cerr << "Enter a positive number: "; cin >> i; if (i<0) throw MyException("Not Positive",i); } catch (MyException e) // catch an error { cout << e.str_what << ": " << e.what << endl; } return 0; }

You can have more than one catch associated with a try. In fact, it is common to do so. However, each catch must catch a different type of exception.

void handler (int test) { try { if (test) throw test; else throw string("Value is zero"); } catch (int i) { cerr << "Caught an integer: " << i << endl; } catch (string str) { cerr << "Caught a string: " << str << endl; } }

As you can see, each catch statement responds only to its own type. In general, catch expressions are checked in the order in which they occur in a program. Only a matching statement is executed. All other catch blocks are ignored.

Grouping of exceptions

You need to be careful how you order your catch statements when trying to catch exception types that involve base and derived classes because a catch clause for a base class will also match any class derived from that base. Thus, if you want to catch exceptions of both a base class type and a derived class type, put the derived class first in the catch sequence. If you don't do this, the base class catch will also catch all derived classes.

#include <iostream> using namespace std; class Base {}; class Derived : public Base {}; int main () { try { Derived d; throw d; } catch (Base e) { cerr << "Caught a base class." << endl; } catch (Derived e) { cerr << "This don't execute." << endl; } return 0; }
Catching all exceptions

In some circumstances you will want an exception handler to catch all exceptions instead of just a certain type. This is easy to accomplish. Simply use this form of catch:

catch (...) { // process all exceptions }

Here, the ellipsis matches any type of data.

void handler (int test) { try { if (test==0) throw test; // throw int if (test==1) throw "one"; // throw char* if (test==2) throw 2.72; // throw double } catch (int e) // catch an int exception { cerr << "Caught an integer." << endl; } catch (...) // catch all other exceptions { cerr << "Caught One!" << endl; } }

One very good use for catch(...) is as the last catch of a cluster of catches. In this capacity it provides a useful default or "catch all" statement.

Exception specifications

You can restrict the type of exceptions that a function can throw outside of itself. In fact, you can also prevent a function from throwing any exceptions whatsoever. To accomplish these restrictions, you must add a throw clause to a function definition. The general form of this is shown here:

ret-type func-name (arg-list) throw(type-list);

Here, only those data types contained in the comma-separated type-list may be thrown by the function. Throwing any other type of expression will cause abnormal program termination. If you don't want a function to be able to throw any exceptions, then use an empty list.

Attempting to throw an exception that is not supported by a function will cause the standard library function unexpected() to be called. By default, this causes abort() to be called, which causes abnormal program termination. However, you can specify your own unexpected handler if you like.

Exception in constructors
Exception in destructors
Resource exhaustion
Standard exceptions

References
  1. B.Stroustrup: The C++ programming language. Third edition.
    Section 14: exception handling; pp. 355-386.
  2. H. Schildt: C++: the complete reference. Third edition.
    Section 19: exception handling; pp. 489-509.