Exceptions

The intent of this article is to provide a quick but complete introduction to C++ exceptions.
The exception handling mechanism, common usage schemes and performance issues of exceptions
versus return codes will all be covered.

So why are exceptions a good thing?

Exceptions separate error handling code from normal code. This is a good thing, because it
makes the code much more readable. Compare a long series of if conditions,
partly for error checking, partly for branching, nested with function calls versus simply
writing down the function calls one after another.

Exceptions cannot be ignored like return values. Although programmers new to exceptions are
often confused by this fact, it is far better to notice an error than letting your
application continue to run into an undefined state, often causing follow-up
errors which make it much harder to find the real cause of a problem.

Exceptions can carry rich error information, specifying the exact cause of an error
and which operations were carried out at that moment. To provide similar features using
error codes, you would be required return entire error structures from each function
called, or to allocate/deallocate additional error information on the heap.

It is possible to derive exception classes from each other. This way, you can categorize
you errors better, while still making it easy for the user to catch an entire kind of
error types (like catching all file access errors, including errors because of locked
files, missing files and corrupted files).

Using exceptions, the return values of your functions can be used for other purposes again.
This is important because it enables your functions to be used in initialization lists of
class constructors or as arguments to other functions, without the need for
temporary variables.

The Problem

The average programmer commonly makes use of return codes to communicate the result of
a function call back to the caller. There’s nothing wrong with it, but it’s just as common
that a large portion of these return codes are not checked at all, sometimes because
the programmer thought it was unlikely that a specific function might fail just there,
sometimes also out of pure lazyness.

If an error goes by undetected, the program will be in an undefined state, doing strange
things or crashing in a place where it is next to impossible to locate the original error
which has caused the instability. To write robust code, all errors have to be detected and
properly handled (or returned to the caller if they cannot be handled in the current scope).
But doing so requires a lot of grunt work, littering each and every function with possibly
dozens of if()s.

Think of exceptions as an automation of this process. When an exception is thrown, the
current function automatically returns to its caller, which has the option to either catch
the exception, or to ignore it, causing the exception to travel further upward in the call
stack. Once the exception leaves the main() procedure without being caught,
the program is aborted.

Introduction

foo() will be called from within main(). Then, an exception will
be thrown by foo(), travel upwards to the caller and further, probably causing
some kind of message from your compiler’s run time library 😉

Notice that the line of code, directly after the call to foo(), will not be
executed because the exception smashes back into the main() function. Next
we’ll try to catch the exception, thus preventing it from aborting our program:

This time, the program won’t crash. The exception we’ve thrown is going to be caught
within main(), display the message contained in the catch block and then
normally continue execution after the catch block. Try commenting out the throw instruction
and look what changes when the program is run.

Of course, a function containing an exception handler can call another function, which also
contains an exception handler. Actually, you can even nest exception handlers directly:

You can see two new things here: A nested try block which will catch the exception that
is thrown before it reaches the outer try block, and another way to use the
throw instruction that allows us to re-throw exceptions inside a catch block:
If you write throw; without any arguments in an exception handler,
the exception that was caught will be re-thrown.

Try commenting out this line and see what happens!

Exception classes

The standard C++ library provides us with a set of exception classes, which is used by
the C++ library functions and which we can use in our own applications. These are made
available to your code by including the stdexcept header.

Some of the exception classes provided by the C++ standard library are:

std::exception – Root exception class. All exception
classes in the standard library are directly or indirectly derived from this class.

std::runtime_error – Base class for all errors
which are only detectable at runtime, like missing files, out-of-memory situations
and the like.

std::logic_error – Base class for all errors which
are caused by a programming error. Reasons for these errors can usually be
permanently resolved. Typical cases are invalid array indices, uninitialized
objects and more.

std::invalid_argument – Derived from logic_error,
this exception is thrown when an invalid argument is encountered. Would be the
right exception to throw if your hex2int() function is called
with a string containing the letters “0xabcw” 😉

std::out_of_range – Derived from
std::logic_error, should be thrown if an index passed as an argument
to a function or method is not within the valid range (eg. exceeds
array boundaries).

There are more exception classes in the standard library which you can look up in your
compiler’s documentation.

There’s nothing preventing you from creating your own exception classes, even the seperation
of runtime_errors and logic_errors may not be all that useful
to you. But let’s stick with the standard exception classes for now, so I’ll be able to
quickly demonstrate the benefits of exception classes.

Both exceptions (invalid_argument and out_of_range) will be
caught in main() because both are derived from the class by which we catch
the exception, logic_error. You can catch an entire category of exceptions,
provided the category shares a common base class by which its exceptions are classified.
Notice that if you re-throw an exception that is caught as one of its base
classes, it will not become an actual instance of the base class.

When using exception classes, always throw by value. Throwing an exception allocated
with new will work, too, but there’s no place where the exception could be
safely deleted again. As shown in the previous example, exceptions thrown
by value can still be caught by reference, thus not preventing full usage of polymorphism.

Should you ever need to catch all exceptions, regardless of their type, there’s a
special argument for catch which allows you to do so:

In the above example, new throws an exception because the amount of memory we’re requesting
is invalid. We can catch this exception without knowing its type.

Using exceptions

Now that we know the mechanics of C++ exceptions, let’s see how and where we can use them.
An exception is thrown, as is indicated by its name, in exceptional situations. Exceptions
should not be used to control normal program flow.

However, some programmers understand an exceptional situation as a situation in which the
application can not continue to run and has to be aborted. If that was what exceptions were
intended for, they would be nothing more than a fancy, but redundant implementation of
the C runtime’s abort() function.

An exceptional situation is exceptional when the current scope cannot recover from it. For
example, a function that is called to load a bitmap, the file name of which being provided
as an argument, will reach an exceptional situation when the file does not exist. It can not
resolve this error on its own, neither does it know whether this bitmap is unimportant and
can be silently replaced by a white square.

For the caller, this situation may not be that exceptional anymore. Maybe the user just
selected an invalid file in the file selector and expects all but the entire program
aborting, trashing all his previous work. From the programmer’s side, this means that
when the image is loaded, after being selected by the user, all exceptions relating to
file accesses can be handled by displaying a little message box to the user and continuing
normal program flow.

Throw exceptions when an error (an exceptional situation) occurs which can not be handled
in the current scope. This is the most important and also the only rule to remember.

Drawbacks

A place where you would not want to use exceptions is within tight loops that have to run
as fast as possible. Even if no exception is thrown, an exception handler can slow down
the speed of function calls and object construction a bit. This is because of the stack
unwinding code which some compilers will generate. I know there are some programmers which
see their entire application as one tight loop which has to run as fast as possible on
every single line of code. I can’t help those 😉

When refactoring existing code to use exceptions, you have to be very careful not to create
situations like this one:

If the senseOfLife() function would be modified to throw an exception when it
fails to find the sense of life, the previous snippet of code would still work, but the
exception would prevent the cleanup code from being run. Apart from memory leaks, you can
generate all kinds of awkward behavior from this exact type of code – try throwing
an exception through WindowProc() and catch it in your message pump for a taste 🙂

This does not mean that there’s an inherent problem with exceptions, just that you should
be careful when equipping projects with exception handling that have been built by an
exception-unaware programmer using return codes originally.