Exception Handling 1 Traditional Error Handling We have already covered several mechanisms for handling errors in C++. We ll quickly review them here. Returning Error Values One classic approach involves returning a predefined error constant from a function on an error condition. For example, the C memory management function malloc() returns NULL for out of memory, and the C file I/O function fopen() returns NULL for unsuccessful open. However, users may not check for an error condition, either because they forget or because they don t even know that they need to do so. For example, the C function printf() returns the number of arguments successfully printed. However, you will probably never encounter code that does anything with the return value of printf. Additionally, choosing a predefined error value is not always possible. For example, if operator[] on an array or vector is out of bounds, there s no value that is guaranteed not to appear in the contents of the array. Global Error Condition Flag Standard C provides errono() and perror() functions to support a global C standard library error code. POSIX, the de-facto standard for how UNIX systems, defined a global integer value errno. However, just as with returning an error value, users may choose to ignore the error code. Error checking bulks up normal code, making it less efficient and harder to read. Exception Handling Exceptions are a mechanism for handling errors out of band, in separate code that obeys scopes and C++ code flow.
Exception Handling 2 class Vector // detects out of range errors as exceptions int sz; public: class Range ; // an excepton class, defined within class Vector int& operator[] (int i) if (0 <= i && i < sz) return p[i]; // within range, OK throw Range(); // exception -- throw a Range object ; operator[] throws an exception, when it detects an out of range subscript i Handling an Exception When an exception is thrown, the error must be caught or else the program will crash. If an exception is uncaught in a particular function, the call stack (the layers of functions calling other functions) is unwound until something that handles the exception is found. In this manner, you can almost imagine an exception to be an alternate means to return from a function.
Exception Handling 3 void foo(vector& v) int i; try bar(v); // try block may or may not cause an exception catch (Vector::Range) // if try causes an exception, then catch a Range object // do something about the exception void bar(vector& v) v[v.size() + 1]; // trigger range error! The above code throws Range up to bar() (no handler here), then foo(), which catches Range. Advantages Instead of terminating the program, we can write more robust, fault-tolerant code. Instead of returning a value representing error (e.g., 0 or NULL) that may not always be feasible if there is no acceptable "error value, we explicitly separate error handling code form "ordinary" code. Exception handling is also more readable and more amenable to automated code tools such as Visual Studio. Instead of returning a legal value and leaving the program in an illegal state, which lets the program runs, but may cause mysterious crashes later on, we force developers to get the program to run acceptably. Exceptions as Objects You can throw any valid value in C++: an int, a double, a bool, a class, etc. However, there are significant advantages to throwing objects. For example, we can pass data members as part of the exception information, such as error codes, a failure message, etc. Something else is nice about exceptions as objects: we can use inheritance for abstraction!
Exception Handling 4 class MathErr ; // Base class for a hierarchy of exceptions class Overflow : public MathErr ; class Underflow : public MathErr ; class ZeroDivide : public MathErr ; Now we can catch any of these exceptions: try /*... */ catch (Overflow) /*... */ catch (Underflow) /*... */ catch (MathErr) /*... */ try can catch various exceptions, distinguishing them by their class names What do you think happens if code in try throws a ZeroDivide exception? MathErr catches it, since it is the base class for ZeroDivide However, a function need not catch all exceptions. Any exception not caught get passed on up the call stack. Exceptions in Constructors Exceptions especially a good idea for constructors because constructors may fail, but cannot produce an error value. Exception handling lets failure be transmitted out of the constructor, e.g.: Vector::Vector(int size) if (size < 0 max < size) throw Size(); // max is member of Vector Code creating Vectors can catch Size errors:
Exception Handling 5 Vector* f(int i) Vector* p; try p = new Vector v(i); catch (Vector::Size) p = new Vector v(vector::max)); return p; //end of f() Standard Exceptions The Standard C++ Library supports a set of standard exceptions. The base language throws exceptions: i.e., new throws bad_alloc, STL container classes such as vector throw out_of_range, invalid_argument, etc. The base class for all of these exceptions is a class called exception. A function what() returns a character representation of an exception. Acknowledgement Much of this lecture series is from Lehigh s introductory computer science course.