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

Raising Exceptions
OSE - C++ Library User GuideRaising Exceptions

Raising Exceptions

1 Handling Errors

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.

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.

2 C++ Exceptions

The purpose of the C++ exception mechanism is to allow you to signal that an error has occurred, by throwing an exception. This exception can be caught higher up in the call chain, where you can make the decision either to terminate, or continue execution of the program. The mechanism of throwing and catching an exception is illustrated by the following example.

  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 your application and produce a `core' image. If your program needs to do something before halting, you can supply a terminate function which will be called by terminate() when it is invoked. To indicate that your terminate function should be called, you should execute the function set_terminate() in your program, passing to it a pointer to your function. For example, the following has the same end result as the example above.

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

void terminate_function()
{
exit(1);
}

main()
{
set_terminate(terminate_function);

function(0);
}
The terminate function you supply should not return. If the function does return, terminate() will still call the abort() function, resulting in your program being halted.

The purpose of the terminate function is to allow you to clean up parts of your application before calling exit(). For example, you may need to remove locks on files, or flush out the contents of any streams. Alternatively, your terminate function could restart the application. You should remember, that your terminate function has been called because an error was encountered, therefore, you should keep to a minimum, anything you do in the terminate function. You should try to avoid accessing any data complex data structures. You should also avoid allocating any new memory while in your terminate function.

If you are using a C++ compiler which does not support exceptions, the library provides the terminate() and set_terminate() functions. When an error is detected, and an exception needs to be thrown, the function terminate() will be called instead. Therefore, although you cannot catch the exceptions, you can specify a terminate function to clean up and either halt or restart the application.

3 Exception Classes

All classes which are thrown as exceptions by the library, derive from the class OTC_Exception. If you want your code to be portable to the same C++ compilers that OSE is, some of which do not support exceptions, exception classes you create, should be derived from the class OTC_Exception. An abbreviated version of the definition of this class is:

  class OTC_Exception
{
public:

OTC_Exception(char const* theError=0);
// <theError> should be a string
// describing the error which has
// occurred. If <theError> is <0>
// then a description of
// <"Unknown error"> is saved. The
// description of the error will
// be displayed on the logger at
// priority level <ERROR>.

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 <outs>. This should be redefined
// in derived class to first call the
// base class version of the function, ie.,
// <OTC_Exception::display()>, and then
// dump out any additional information which
// is kept in the derived class. The derived
// class must terminate each line of
// information with an <endl>.
};
Any derived classes should redefine the display() member function to dump out to a stream a representation of the exception. As the OTC_Exception base already dumps out some information, your display() function, should call the base class version of the function as the first thing it does. To raise an exception, instead of using throw(), you must use the function OTCLIB_THROW(), passing the exception as an argument. For example:

  if (theString == 0)
OTCLIB_THROW(OTC_Exception("Invalid input"));
or:

  if (theString == 0)
{
OTC_Exception exception("Invalid input");
OTCLIB_THROW(exception);
}
If your C++ compiler supports exceptions, the OTCLIB_THROW() function will use throw() to raise a true C++ exception of the type you passed to the function. If you wish 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);
...
}
If your C++ compiler does not support exceptions, a representation of the exception will be automatically displayed via the logger using similar code to that given above. This will be followed by the call to the terminate() function supplied with the library.

4 Preconditions

When you use the classes in the 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. If you wish to generate an exception for failure of a precondition, rather than using the exception class you should use the macro OTCLIB_ENSURE(). For example:

  #include <OTC/OTC.h>

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

// ...
}
Using the OTCLIB_ENSURE() macro means that you do not have to know if your C++ compiler supports exceptions. If your C++ compiler does support exceptions, the exception class will be thrown. If your C++ compiler does not support exceptions, a description of the exception will be displayed, and the terminate() function supplied with the library will be called instead.

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

As OTCLIB_ENSURE() is a macro, you should avoid using strings in the expression passed as the first argument. This is because not all preprocessors can correctly handle the embedded string when converting the expression into a string, that can be displayed. Both the OTCLIB_ENSURE() macro and otclib_ensure() function may be used in C code.

5 Assertions

Similar to the precondition checks, assertions check for the success or failure of a condition. The difference between an assertion check and the precondition check, is that the assertion check is generally only used in development to check that the program is behaving correctly. Assertion checks generally will be removed from a program in the final version. The exception type created in the event of a failed assert is OTCERR_AssertionFailure. An assertion check would be coded in the following manner using the OTCLIB_ASSERT() macro.

  #include <OTC/OTC.h>

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

// ...
}
Rather than remove assertion checks from code when producing a final version of a program, they can be conditionally compiled out of the program by defining the preprocessor symbol NDEBUG. Because assertions can be compiled out of a program, the expression used in the assertion should not have any side effects on which you rely for the normal operation of your program.

If you are using makeit, and it has been installed in the standard manner, using the `opt' variant will automatically result in the NDEBUG symbol being defined by the preprocessor.

6 Memory Exhaustion

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

If you wish to generate an exception when memory is exhausted, you should add the following to the main() routine of your program.

  set_new_handler(otclib_new_handler);
When called, the function otclib_new_handler(), will create an exception of type OTCERR_OutOfMemory.

7 General Exceptions

An alternative to the precondition and assertion macros is the OTCLIB_EXCEPTION() macro. This can be used anywhere you wish to raise an error. The only argument which the macro accepts is a string which describes the error, so you must perform any condition check explicitly. For example:

  #include <OTC/OTC.h>

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

// ...
}
The type of the exception created is OTC_Exception.

8 Home Grown Exceptions

If you have created your own exception mechanism, it is possible for the library to throw an exception using your mechanism. To do this, you need to register a function to be called by the library when it needs to throw an exception. The function you supply must throw an exception using your mechanism and not return. If the function does return, the library will call the terminate() function.

To specify your exception function, you should call the function OTC_Exception::setThrow() at the start of your main() function, passing it a pointer to your function. Your 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);

// ...
}

9 Termination from C Code

Although it is possible to provide only C++ code to be invoked in the event of program termination, it is possible to force termination from C code as well as C++ code. The function which you should use in C code to terminate your program is called OTCLIB_TERMINATE(). When called, this function will call the terminate() function. You may also call this function in C++ code.

An example of using the function in C code is included below. The C code file should include the file `OTC/OTC.h' to access the definition of the OTCLIB_TERMINATE() function.

  #include <OTC/OTC.h>

function(theString)
char* theString;
{
if (theString == 0)
OTCLIB_TERMINATE();

// ...
}