OSE - C++ Library User Guide

Graham Dumpleton
Dumpleton Software Consulting Pty Limited
PO BOX 3150
Parramatta, 2124
N.S.W, Australia
email: grahamd@nms.otc.com.au

Table of Contents

Exception Cleanup
OSE - C++ Library User GuideException Cleanup

Exception Cleanup

1 Terminate Action

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 you to register a function which will be called when exit() is invoked. As this is not widespread, the terminate() and set_terminate() functions which were described previously, should be used instead when you are programming in C++. By using these functions, you are ensured that any cleanup actions you define will also be executed when an exception is thrown, but not caught, as this results in the terminate() function being called.

2 The TObject Class

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 you may need 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. In versions prior to version 4.0, this class was called OTC_Terminate. The definition of the class is listed below.

  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 result of
// abnormal program termination.
};
If any of your classes must do something special, if the program is terminated abnormally, you should derive your class from OTC_TObject and define the cleanup() function. When you create an instance of your 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 that the terminateAll() function is invoked, when the terminate() function is called, you must register the terminateAll() function. This is done by calling the set_terminate() function.

  #include <OTC/debug/trmnate.hh>

main()
{
set_terminate(OTC_TObject::terminateAll);
// ...
}
An example of a class which would be derived from the TObject class is shown below. 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;
};
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 was 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(), you should 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 your program should be restarted, rather than calling exit() you should execute your program again.

A version of the terminateApp() function is supplied in the library. This function is called otclib_terminate_function() and as well as calling terminateAll() and exiting, will send a warning to the message log system indicating that the program is terminating. The definition of the function is:

  void otclib_terminate_function()
{
OTC_TObject::terminateAll();
OTC_Logger::notify(
OTCLIB_LOG_ALERT,
"Program terminating");
exit(1);
}
To use this function, add the call

  set_terminate(otclib_terminate_function());
at the start of your programs main() routine.

3 Stack Unwinding

When an exception occurs, and the stack unwound, only destructors for objects created on the stack are invoked. If you had created an object using operator new(), and only held 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. The code which could potentially throw an exception is called. If an exception occurred, the destructor of the reaper class would be called, resulting in the object on the heap being destroyed. After the point in the code where the exception could be thrown the reaper class is made to release the object on the heap. Provided the object is released, it would not be deleted via the reaper object when that code block exited.

An example of the OTC_Reaper class is:

  void function()
{
OTC_Reaper<Object> 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.