Bad things can happen even to good programs. When they do, you must confront the potentially messy, unpredictable, and painful topic of error handling. You can take some consolation in knowing that every developer goes through it. Also, the Rogue Wave class libraries use an extensive and complete error handling model to alleviate your distress.
In Math.h++, errors are divided into three categories:
Violated preconditions
Invalid input
Asynchronous errors.
The difference between them depends on who made the error: the programmer, the final end user, or some external/internal software error. Each type of error requires a different type of handling. The following sections describe these approaches and when you can change how they are handled.
>Violated precondition errors involve failures to follow the programming rules set out by the Math.h++ classes. If an indexing operator says not to ask for an index greater than a vector's length and you do so anyway, that is a violated precondition.
A more formal description is given by Meyer (1988). In summary, Meyer says that a function is regarded as a contract between the caller and the callee. If the caller abides by a set of preconditions, then the callee guarantees to return results that satisfy a set of postconditions. Typically, preconditions are such things as requiring that an index be within a set of bounds, that pointers be non-nil, and so on. The ideal goal is that these types of errors never happen. If they never happen, there is no need to provide a recovery mechanism or even to detect them!
But, of course, they do happen. So, how do you guard against them? And how do you detect them? The Rogue Wave classes have an extensive set of PRECONDITION and POSTCONDITION clauses at the beginning and end of critical functions. They can be activated by compiling the entire library with the preprocessor flag RWDEBUG defined.
NOTE: The entire library must be compiled with a single setting of the flag-either defined or not defined.
If compiled with the RWDEBUG flag defined, the resultant library will be slightly larger and slightly slower. See the appropriate makefile for additional directions.
The preconditions and postconditions are implemented either through the Rogue Wave error facility or with asserts. With asserts, a failure causes the offending condition to be printed out, along with the file and line number where it occurred.
Again, the assumption is that these are programming errors and can and should be avoided. Hence, there are no facilities for error recovery.
>An invalid input error is usually caused by the final end user. For example, you might use class RWGenFact<T> to construct an LU factorization from a matrix that turns out to be singular. It is a mean-spirited program that simply aborts when given such a matrix. It is preferable if the factorization's validity can be explicitly checked, to be rejected or corrected.
The line between a violated precondition and an invalid input can be unclear. For example, the rules could say, "Don't give me a singular matrix," and then the programmer is responsible for detecting the matrix's singularity before handing it to the RWGenFact<T> constructor. Of course, this is a lot of work, and in this example the RWGenFact<T> class is better equipped than the caller to determine the singularity of the matrix. Hence, this is an approach that is generally not taken.
How are these kinds of errors dealt with? Generally, either by returning a special value, such as a nil pointer, or by using a member function that tests the validity of the resultant object. The following code continues the RWGenFact<T> example:
// This matrix is hopelessly singular: RWGenMat<double> a(8, 8, 1); RWGenFact<double> fact(a); if( fact.fail() ) cerr << "Non-factorable matrix" else { // ... Use it }
The above program prints out Non factorable matrix. Like many other classes, class RWGenFact<T> has several member functions for testing its validity.
>Difficult to predict, asynchronous errors are generally due to a failure on the part of the operating system. By far the most common example is "out of memory." Other asynchronous errors are failure to open a file, perhaps due to lack of disk space, or hardware failure.
The usual response to these types of errors by Math.h++ is to throw an exception. If exceptions are not available, the default error handler posts a note to standard output with the type of error, then aborts the program. This default handler can be changed.
>When an exception is thrown, a throw operand is passed. The type of the throw operand determines which handlers can catch it. Math.h++ uses the following hierarchy for throw operands:
Table 7 -- Throw Operand Hierarchyxmsg RWxmsg RWInternalErr RWBoundsErr RWExternalErr RWFileErr RWStreamErr xalloc RWxalloc
This hierarchy assumes the presence of class xmsg, nominally provided by your compiler vendor. Class xmsg carries a string that can be printed out at the catch site to give you some idea of what went wrong. This string can be formatted and internationalized as described in the Tools.h++ User's Guide chapter on internationalization. If your compiler does not come with versions of xmsg and xalloc, the Rogue Wave classes RWxmsg and RWxalloc can emulate them for you.
>Math.h++ uses the macro RWTHROW to throw an exception. If your compiler supports exceptions, this macro resolves by calling a function that throws the exception. If your compiler does not support exceptions, the macro resolves to call an error handler with prototype:
void errHandler(const RWxmsg&);
The default error handler aborts the program. You can change the default handler with the function:
typedef void (*rwErrHandler)(const RWxmsg&); rwErrHandler rwSetErrHandler(rwErrHandler);
The next example demonstrates how a user-defined error handler works in a compiler that doesn't support exceptions:
#include <rw/rwerr.h> #include <rw/coreerr.h> #include <iostream.h> #ifdef RW_NO_EXCEPTIONS void myOwnErrorHandler(const RWxmsg& error){ cout << "myOwnErrorHandler(" << error.why() << ")" << endl; } int main(){ rwSetErrHandler(myOwnErrorHandler); // Comment out this line>
// to get the default
// error handler. RWTHROW( RWExternalErr(RWMessage( RWCORE_GENERIC,
12345, "Howdy!") )); cout << "Done." << endl; return 0; } #else //RW_NO_EXCEPTIONS #error This example only for compilers without exception handling #endif
The error numbers are listed in the header files <rw/coreerr.h> and
<rw/matherr.h>. You can see the associated error messages and auxiliary information in files coreerr.cpp and matherr.cpp, and surmise the auxiliary information contained in the va_list. For example, the error RWMATH_INDEX is listed in matherr.cpp as:
"[INDEX] Index (%d) out of range [0->%d]"
The first argument in the va_list is the index the user attempted to use, and the second is the highest allowed index.
>©Copyright 1999, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.