University of Virginia, Department of Computer Science
CS655: Programming Languages
Spring 2000

Yves LEPOUCHARD
21 March 2000

Exception handling in C++


Introduction
Overview
Exceptions as objects
Memory exhaustion
Termination
References

Introduction

There are two main approaches to handling errors in an application. The first approach is to take steps to recover in some way from the error, and resume execution of the application. The second approach is to close down critical parts of the application and either terminate or restart the application.

There are a lot of C++ compilers and the way a C++ program is able to handle exceptions depends on the compiler you consider. If your C++ compiler does not support exceptions, you will not be able to resume the execution of the application under all circumstances. Until your C++ compiler supports exceptions, you will need to resort to conventional programming techniques to be able to cause resumption in the event of an error.

Note: For the user of makeit, there is an easy way to determine if a compiler does support exception handling or not. If the preprocessor symbol HAVE_EXCEPTIONS is defined, the compiler allows users to catch exceptions.


Overview of C++ exceptions

We are considering a C++ compiler handling exceptions. The exceptions in C++ can be thrown by a classic try/catch structure. This exception can be caught higher up in the call chain, where we can make the decision either to terminate, or continue execution of the program. Here is an example of a simple try/catch piece of code:

                 void function(char const* theString)
                 {
                     if (theString == 0)
                     throw exception("Invalid input");
                 }

                 main()
                 {
                     try
                     {
                         function(0);
                     }

                     catch (exception)
                     {
                         exit(1);
                     }
                 }

If an exception is not caught, the function terminate() is called. The default behaviour of the terminate() function is to call the abort() function, which will halt the application and under UNIX produce a 'core' image. If the program needs to do something before halting, it is possible to supply a terminate function which will be called by terminate() when it is invoked. To indicate that a user-defined terminate function should be called, the function set_terminate() should be executed in the program, passing to it a pointer to the user-defined function. For example, the following has the same end result as the example above in as much as the program will terminate through a call to exit().

                 void function(char const* theString)
                 {
                     if (theString == 0)
                     throw exception("Invalid input");
                 }

                 void terminate_function()
                 {
                     exit(1);
                 }

                 main()
                 {
                     set_terminate(terminate_function);
                     function(0);
                 }

Note: The terminate function supplied by the user should not return. If the function returns, terminate() will still call the abort() function, resulting in the abortion of program.

The purpose of the terminate function is to clean up parts of an application before calling exit(). For example, it allows to remove locks on files, or to flush out the contents of any streams. Alternatively, this terminate function could restart the application. However, considering the fact that the terminate function has been called because an error was encountered, it is suitable to keep to a minimum anything this function does and, to avoid accessing any complex data structures or allocating any new memory.

Now, if we consider a compiler which does not support exceptions, we can use the OSE library which provides versions of the terminate() and set_terminate() functions. When an error is detected, the function terminate() will be called instead of raising an exception. Therefore, although the compiler does not allow to catch exceptions, it is still possible to specify a terminate function to clean up and either halt or restart the application.

This overall presentation of the exception handling of C++ has given a first idea of the tools a programmer can use to manage exceptions. It should be thoroughly completed in the next weeks. We notably plan to tackle exception classes, memory exhaustion and terminations.


Exceptions as objects

The object-oriented design of C++ tends to treat the most possible entities as objects. Exceptions are not an exception to the rule. All classes which are thrown as exceptions by the OSE library, derive from the class OTC_Exception. To make a program portable to the same C++ compilers as OSE's, some of which do not support exceptions, it is important to derive any new exception class from the class OTC_Exception. Here is an abbreviated version of the definition of this class:
                 class OTC_Exception
                 {
                   public:
                     OTC_Exception(char const* theError=0);
                       //  should be a string
                       // describing the error which has
                       // occurred. If  is <0>
                       // then a description of
                       // <"Unknown error"> is saved. The
                       // description of the error will
                       // be displayed on the logger at
                       // priority level .

                     char const* error() const;
                       // Returns a description of the
                       // error which has occurred.

                     virtual void display(ostream& outs) const;
                       // Dumps a message which composes together
                       // all the information about the error on the
                       // stream . This should be redefined
                       // in derived class to first call the
                       // base class version of the function, ie.,
                       // , and then
                       // dump out any additional information which
                       // is kept in the derived class. The derived
                       // class should terminate each line of
                       // information with an  and not use 
                       // or .


                 protected:
                   virtual void throwIt()const;
                       // Must be redefined in a derived class. The
                       // derived implementation must contain the
                       // code .
                 };

A derived exception class must redefine some functions of this defintion. For example, it must override the throwIt() function. The implementation of this function in any derived class should contain:
                 #if defined(HAVE_EXCEPTIONS)

                   throw *this;

                 #endif

The reason of this rewriting is the inability of some compilers to deal with derivation mechanism. When an exception is thrown, the throwIt function used belongs to the most derived type in the exception hierarchy. If a derived class does not provide its own function, the exception is going to be thrown as a reference to the base class type. Some compilers truncate the class object and only use the base class type. Thus any information in derived classes is lost and exceptions can be caught only through the base class type.

Any derived classes should also redefine the display() member function to dump out to a stream a representation of the exception. As the OTC_Exception base class already dumps out some information, the display() function of a derived class should first call the base class version of this function.

To specify a specific exception class while throwing an exception, the function OTCLIB_THROW() must be used instead of throw(). OTCLIB_THROW() takes the exception class as argument. Here is an example throwing OTC_Exception:
                 if (theString == 0)
                   OTCLIB_THROW(OTC_Exception("Invalid input"));

               or:

                 if (theString == 0)
                 {
                   OTC_Exception exception("Invalid input");
                   OTCLIB_THROW(exception);
                 }

In the case of a C++ compiler supporting exceptions, the OTCLIB_THROW() function will use the throwIt() member function described above, to raise a true C++ exception of the type specified to the function. In order to know what the exception was, any code which catches the exception should dump a representation of the exception to a stream by calling the display() member function. For example, to display a representation of the exception class to the message log facility, the catch clause would be written as:
                 catch (OTC_Exception& theException)
                 {
                   char theBuffer[2048];
                   OTC_LogStream theStream(theBuffer,sizeof(theBuffer));
                   theException.display(theStream);
                   theStream.flush();
                   ...
                 }

Another possibility is to set the OTCLIB_LOGEXCEPTION environment variable before running the program. Doing this causes the OTCLIB_THROW() function to always display information about the exception via the logger whether or not it is caught. In the case of a C++ compiler which does not support exceptions, a representation of the exception is always automatically displayed via the logger regardless of whether the environment variable is set. It is followed by the call to the terminate() function supplied by the OSE library.

Preconditions

When using the classes in the OSE library, various preconditions exist which must be satisfied before calling any member functions of the class. If the preconditions of a function are not satisfied, the exception type OTCERR_PreconditionFailure is thrown. It is possible to generate a user-defined exception for failure of a precondition by using the macro OTCLIB_ENSURE() rather than exception class. For example:
                 #include 

                 void function(char const* theString)
                 {
                   OTCLIB_ENSURE((theString != 0), "function() - Invalid input");
                   ...
                 }

OTCLIB_ENSURE() macro is worth using because it automatically supplies information about the name of the file and line in the file where the macro is used.

The first argument to the OTCLIB_ENSURE() macro should be an expression which yields a non zero value if the condition is satisfied or, zero if the condition is not satisfied. The second argument to the macro should be a description explaining why the precondition failed.

Note: The macro OTCLIB_ENSURE_W() can be used in order to get the same style of error message as OTCLIB_ENSURE() generates and sends to the logger, but without stopping the application. It takes the same arguments but regards it as a warning and not an error and thus does not throw an exception.

Assertions

Assertions checks for the success or the failure of a condition are similar to the precondition checks. The difference between an assertion check and a precondition check is that an assertion check is generally used in development to check that the correctness of the program behaviour. Assertion checks are generally removed from a program at the final version. The exception type created in the event of a failed assert is OTCERR_AssertionFailure. An assertion check can be coded in the following manner using the OTCLIB_ASSERT() macro.
                #include 

                 void function(char const* theString)
                 {
                   OTCLIB_ASSERT(theString != 0);
                   ...
                 }


An interesting feature of C++ is able to compile out the assertion checks out of the program when producing its final version. To activate this feature, the preprocessor symbol NDEBUG has to be defined. Because assertions can be compiled out of a program, the expression used in these assertions should not have any side effects on which the normal operation of the program relies.

General exceptions

An alternative solution to the precondition and assertion macros is the OTCLIB_EXCEPTION() macro. It can be used anywhere raising an error is needed. The only argument accepted by the macro is a string which describes the error so, any condition check must be performed explicitly. For example:
                 #include 

                 void function(char const* theString)
                 {
                   if (theString == 0)
                     OTCLIB_EXCEPTION("function() - Invalid input");
                     ...
                 }

Note: the type of the exception used is OTC_Exception.

Integration of user-defined exceptions

It is possible for the library to throw user-defined exception. The first step is to register a function to be called by the library when it needs to throw an exception. The supplied function must throw an exception using the user-defined exception mechanism and it must not return. If the function does return, the library calls the terminate() function.

To specify such a function, the user should call the function OTC_Exception::setThrow() at the start of the main() function passing it a pointer to your function. The exception function must take a single argument of the library exception which is being raised, and return 'void'. For example:
                 void throwMyException(OTC_Exception const& exception)
                 {
                   // throw an exception using home grown mechanism
                 }

                 main()
                 {
                   OTC_Exception::setThrow()(throwMyException);
                   ...
                 }


Memory exhaustion

When memory is exhausted, operator new() is unable to allocate any memory and it returns a nil pointer. To cater to this, the pointer returned by operator new() could always be checked. Alternatively, it is possible to register a function which will be called by operator new() when memory is exhausted. The function supplied can attempt to attain more memory, or it can terminate the program.

To generate an exception when memory is exhausted, the following statement should be added to the main() routine of the program.
                 set_new_handler(otclib_new_handler);

When called, the function otclib_new_handler() creates an exception of type OTCERR_OutOfMemory.
However, this mechanism is being obsoleted by the developing C++ standard which throws exceptions by default. Besides some current C++ compilers do not provide set_new_handler(). Thus for portability purposes, the solution presented above should not actually be used.


Termination

The method used by most programmers to terminate a program written in C or C++ is to call the operating system exit() function. This will cause program termination, however by itself, this does not allow for any special actions to be undertaken in order to return the system to a stable state first.

Take for example, an application which places locks on files which it is using. If this were to encounter some unrecoverable error mid way through an operation, it would need to be able to remove those locks and release the files for use by other applications. The problem with using exit() directly is that knowledge of how to remove the locks is most likely not known at the point where exit() is being invoked.

A complimentary function to exit() is available on some systems. This is called on_exit() or atexit(), and allows the user to register a function which will be called when exit() is invoked. As this is not widespread, the terminate() and set_terminate() functions described previously, should be used instead when programming in C++. The use of these functions ensures that any user-defined cleanup actions will also be executed when an exception is thrown, but not caught, as this results in the 'terminate()' function being called.

The set_terminate() function provides a mechanism to register a function to be executed when terminate() is called, however, it is only possible to register one function. As it may needed to perform a number of actions when terminate() is called, an object based registration scheme for actions to run is supplied. This is implemented by the OTC_TObject class.
                 class OTC_TObject
                 {
                   public:
                     static void terminateAll();
                       // Iterates through all instances of
                       // this class and invokes cleanup()
                       // on each instance.

                   protected:
                     virtual void cleanup() = 0;
                       // Should be redefined in a derived
                       // class to perform any actions which
                       // need to be done in the event of
                       // abnormal program termination.
                 };
If any of the user-defined classes must do something special in the case of an abnormal termination of the program, these classes should be derived from OTC_TObject and, the cleanup() function should be defined. When one creates an instance of one class, it will be linked with any other classes, which also derive from the OTC_TObject class. The terminateAll() function, when called, will iterate through this list of classes and call the cleanup() function on each.

In order to invoke the terminateAll() function when the terminate() function is called, it is important to register the terminateAll() function. This is done by calling the set_terminate() function. Here is an example of code to illustrate it:
                 #include 

                 main()
                 {
                   set_terminate(OTC_TObject::terminateAll);
                   ...
                 }

And shown below is an example of a class which would be derived from the TObject class. In this example the cleanup() function ensures that the open file is closed and all data still in the stream is written out. If the file had a lock on it, the cleanup() could also have removed the lock. This is not shown in the example though.
                 class EX_Log : private OTC_TObject
                 {
                   public:
                     EX_Log(char const* aFile) : myFile(aFile) {}

                   private:
                     void cleanup() { myFile.close(); }
                     ofstream myFile;
                 };

It is recommended that the cleanup() function should do as little as necessary. It should not try and allocate any memory, as it may be because of memory exhaustion that the program is being terminated.

The example above shows the function terminateAll() being registered as the function to call when terminate() is invoked, normally however, this would not be done. The reason for this is that the terminateAll() function returns. If a function returns to the terminate() function from which it is called, the terminate() function will call abort(), which results in a core file being produced. Instead of registering terminateAll(), it is better to register a wrapper function which will call terminateAll() and then call exit(). For example:
                 void terminateApp()
                 {
                   OTC_TObject::terminateAll();
                   exit(1);
                 }

                 main()
                 {
                   set_terminate(terminateApp);
                   ...
                 }


If the user wants to restart the program, rather than calling 'exit()', this program should be executed again.

A version of the terminateApp() function is supplied in the library. The function is called otclib_terminate_function(). As well as calling terminateAll() and exiting, the version of the function supplied in the library, will send a warning to the message log system indicating that the program is terminating. Here is the definition of this function:
                 void otclib_terminate_function()
                 {
                   OTC_TObject::terminateAll();
                   OTC_Logger::notify(OTCLIB_LOG_ALERT, "Program terminating");
                   exit(1);
                 }


To use this function, it is necessary to add the following call at the start of the program main() routine.
                 set_terminate(otclib_terminate_function);

Stack unwiding

When an exception occurs, and the stack unwound, only destructors for objects created on the stack are invoked. If an object had been created using operator new() and holding a pointer to that object via a stack variable, the object will not be destroyed, resulting in a memory leak. Three different classes are provided to assist in ensuring that objects allocated using operator new() or malloc() are destroyed when an exception occurs. These classes are OTC_Reaper, OTC_VecReaper and OTC_MallocReaper.

The concept behind these classes is that, after an object has been created on the heap, it is grabbed by an instance of one of the reaper classes. The reaper class is created on the stack. If an exception occurs, the destructor of the reaper class is called, resulting in the object on the heap being destroyed. This mechanism is only valid all over the code identified as possibly throwing exceptions and specified by the instance of the reaper class. After the point in this specific code, the reaper class is made to release the object on the heap. Provided the object is released, it will not be deleted via the reaper object in the case of an abnormal termination. Here is an illustration of this concept.
                 void function()
                 {
                   OTC_Reaper xxxObject;
                   Object* theObject = new Object;
                   OTCLIB_ASSERT(theObject != 0);
                   xxxObject.grab(theObject);

                   ... code which could throw an exception

                   xxxObject.release();

                   ... code which subsequently saves away object
                   ... or deletes the object
                 }



The OTC_Reaper and OTC_VecReaper classes are templates and are used respectively for pointers to a single object, and pointers to an array of objects. The OTC_MallocReaper is not a template, and is used for memory which has been allocated using the malloc() function.


References

C++ Library User Guide

This source of information is a reference about C++ and provides almost all of the text of this section. However, this section is not an exact copy of the chapter of the user guide about exception handling. I rewrote some paragraphs to made them hopefully clearer. All the examples are extracted from this source of information.

Integrating Structured Exception Handling in the C++ exception mechanism
by Ruurd Pels

This personal web page gives an interesting example of a user-defined structured exception handling integrated in the C++ exception mechanism. It is proposed by Ruurd Pels who is a Dutch technical consultant in software engineering. He concludes that both Borland C++ 5.02 and Borland C++ Builder are compatible and effective in dealing with exception handling.


CS 655 University of Virginia
CS 655: Programming Languages
uvacs655@egroups.com