C++ Exceptions

Written by Gordon Zeglinski

Introduction
So what happened to the rest of the screen saver DTS series? Seems life is full of mini-disasters up here. Last month I injured my shoulder which made typing impossible. This month I had to reinstall OS/2 and reformat the hard disk. I have not yet reinstalled the SOM toolkit or Metaware's High C/C++. Until I have time to do so, the DTS articles will have to wait. Instead, we will talk this month about C++ exceptions.

C++ exceptions are a powerful method of handling special cases. Traditionally, one might do something like: Figure 1: Traditional method of error code handling.

Depending on the return value from foo, an error condition may or may not exist. Exceptions follow a similar concept but are far more powerful.

What is Exactly is a C++ Exception?
An exception is an object designed by the programmer specifically to relay error information. They can be organized in hierarchies, as in the following example: Figure 2: Sample exception hierarchy.

An advantage of using heirarchies of error objects is that any error object with a parent can be caught by blocks expecting the parent object. We'll look at catching exceptions by the exception object's parent class later.

Try, Catch and Throw
"Try" blocks are used to "catch" exceptions that are thrown in functions called within the block. We have already seen that an exception is nothing more than an object designed to convey error information. Unlike the simple switch case, however, try blocks can be nested within and across function calls. Also, exceptions don't rely on the function returning a value. The following sample code illustrates a nested exception. Figure 3: Nested exception example.

Function foo1 calls function foo2. Any exceptions rethrown by foo2 or that are not handled by foo2 can be caught by foo1. In fact, the statement "catch (...)" in foo1 catches all exceptions that aren't explicitly handled. In this case, any exception other than those in the ErrorBase family are caught by the catch (...) block. Function foo3 illustrates how to throw an exception.

Formal Exception Declaration
In order to make sure that a function only throws certain exceptions, C++ allows a list of exceptions to be given with the function declaration. Rewriting foo3 we have: Figure 4: Declaring a method to throw specific exception classes.

Now foo3 can only throw the exceptions ErrorBadArg and ErrorNoMem. An attempt to throw any other exception will result in a runtime error. This makes it easier for the programmer to use third party libraries. The program will not encounter any surprise exceptions because the set of exceptions which a routine may throw is guaranteed.

Note: foo3 must catch all exceptions in any routine it calls other than the two it declares as those to be thrown by it.

Runtime Exception Errors
There's two runtime errors that can occur in the exception handling code. First, the exception might not be caught by any try/catch block. Second, an exception that wasn't in the declaration list for a function might not be caught by the function or a programmer may try to throw an unlisted exception. In the first case, the "terminate" function is called, in the second, the "unexpected" function is called. The functions set_terminate and set_unexpected can be used to replace the default function with application specific ones. The following sample shows how to use set_terminate. Figure 5: Using set_terminate.

In the above sample, if an exception occurs in foo3, My_Terminate should be called because we didn't set up a catch/try block.

Note: set_unexpected is used in the exact same way as set_terminate.

Gotchas
There are two areas to watch out for. The first one is resource leaks. The second one is an overreliance on exceptions to alter the programs flow. Let's look at the following sample function. Figure 6: Resource leaks with thrown exceptions.

There are 2 potential resource leaks in this function. Because the throw alters the programs flow, when the throw statement is executed the mutex semaphore will not be released and the allocated memory will not be returned to the heap. There are several different ways of fixing this problem. The easiest is to move the allocation and semaphore request to after then throw statement. Figure 7: Removing the leak by relocating the resource allocation.

Another approach would be to release the semaphore and free the memory before throwing the exception. Figure 8: Removing the leak by freeing the resource before throwing an exception.

Finally, the most thorough way is to use objects to handle the memory and semaphore. Figure 9: Removing the leak by performing resource management in the constructor and destructor.

This last approach has a hidden advantage. Suppose we called some other functions in foobar and one of these functions threw an exception. What would happen in either of our first 2 versions of foobar? The answer is that we would have the same resource leak that we fixed in the last 2 versions. Unless we add a try/catch block in foobar and then place calls to DosReleaseMutexSem and delete inside the catch block, we will leak resources. Encapsulating the resources and using constructors/destructors to handle their allocation and deallocation provides a safer alternative than manually maintaining the deallocation of resources in several possible exit points.

The second pitfall is using exceptions too liberally. Suppose we have a function that has a fixed set of valid results. Let's say that one result happen most of the time, but it is possible in some cases for the others to occur. Should we use exceptions? The answer is no. Exceptions should only be used to indicate error conditions, not to indicate which action a function performed. As we seen from the C++ compiler benchmarks several issues ago, throwing exceptions is expensive. Not only is throwing the exception expensive, but setting up a try block alone can have noticeable overhead. To keep performance acceptable, exceptions should only be used to indicate an error condition.

Wrapping Things Up
This concludes our look at C++ exception handling. We covered how to define exceptions, catching, and throwing exceptions. Also, we seen how to handle unexpected results by using the set_terminate and set_unexpected functions. Exceptions provide a formal method for handling and declaring error conditions. They have significant run time cost and should only be used where necessary.