Go to the previous, next section.
#include <nihcl/Exception.h>
Classes
ExceptionTrap
,
ExceptionAction
,
AbortException
,
RaiseException
, and
Catch
are related to error handling and the function
NIHCL::setError()
. They were designed as an early experiment in implementing exceptions in C++, and will eventually be replaced by the native C++ exception handling mechanism.
Using the NIHCL exception mechanism is slow and error prone, and is not recommended.
Class
ExceptionTrap
establishes an error handling function for
setError
to call when an error occurs. This error handling function may return, in which case error processing will proceed normally and either abort the program or raise an exception, or it may abort the program immediately.
An error handling function has type
exceptionTrapTy
, defined as:
typedef void (*exceptionTrapTy)(unsigned& /* error code */,
int& /* severity */);
For example, an error handling function
xtrap()
would be defined as follows:
void xtrap(unsigned& error, int& sev) { // ... }
The arguments
error
and
sev
are the same as those passed to
NIHCL::setError()
(see
nihclerrs
).
Creating an instance of class
ExceptionTrap
establishes an error handling function for the remainder of the block in which it occurs:
{ ExceptionTrap x(xtrap); // NIHCL::setError() will call xtrap() until x goes out of scope }
When an instance of class
ExceptionTrap
is destroyed, it restores any previously established error handlers, so such blocks may be nested.
Class
ExceptionAction
is the abstract base class for classes
AbortException
and
RaiseException
. The default action taken by
setError()
is to abort the program; however,
setError()
can raise an exception as an alternative. Derived classes
AbortException
and
RaiseException
control the behavior of
setError()
on a block-by-block, individual error code basis. Class
ExceptionAction
holds the number of the error code being controlled and the previous action (ABORT
or
RAISE
).
Class
AbortException
causes the occurrence of a specific error to result in program termination. For example:
{ AbortException x(NIHCL_CLTNEMPTY); // NIHCL::setError(NIHCL_CLTNEMPTY, ...) will cause program termination // until x goes out of scope }
When an instance of class
AbortException
is destroyed, it restores the previous exception action for the error code, so such blocks may be nested.
Class
RaiseException
causes the occurrence of a specific error to raise an exception to be handled by
BEGINX ... ENDX
. For example:
{ RaiseException x(NIHCL_CLTNEMPTY); // NIHCL::setError(NIHCL_CLTNEMPTY, ...) will cause an NIHCL_CLTNEMPTY // exception to be raise until x goes out of scope }
When an instance of class
RaiseException
is destroyed, it restores the previous exception action for the error code, so such blocks may be nested.
Exceptions are handled by means of the
BEGINX
...
EXCEPTION
...
ENDX
construct, for example:
Object* Property::get( const Object& ob, // object with property const String& name) // name of property { RaiseException x(NIHCL__KEYNOTFOUND); BEGINX return ((Dictionary*)prop.atKey(ob))->atKey(name); EXCEPTION case NIHCL__KEYNOTFOUND: return Object::nil; default: RAISE(EXCEPTION_CODE); ENDX }
The
RaiseException
declaration causes
KEYNOTFOUND
errors to raise an exception rather than causing program termination. Calling the preprocessor macro
RAISE
with the error code as an argument raises an exception.
Statements between the
BEGINX
and
EXCEPTION
macro calls may cause an exception to be raised, in which case control is tranferred to the statements between the
EXCEPTION
and
ENDX
macro calls. These statements form the body of a
switch
statement that tests the value of the error code causing the exception. In this example,
KEYNOTFOUND
exceptions cause the function to return the pointer to the
Nil
object.
Control normally passes to the statements following the
ENDX
whether or not an exception occurs, and whether or not an exception is handled. However, an exception handler can itself raise an exception, causing control to transfer to an enclosing
EXCEPTION
...
ENDX
construct. In this example, the
default
case of the
switch
body causes exceptions other than
KEYNOTFOUND
to be propagated to an enclosing exception handler by re-raising the current exception with the statement
RAISE(EXCEPTION_CODE)
.
When an exception is raised,
longjmp()
is used to transfer control to the most recently established exception handler, causing class instances that go out of scope as a result to be destroyed without their destructors being called. This must be avoided since it violates the C++ guarantee to call these destructors.
Class
Catch
was devised in an attempt to solve this problem. The programmer is responsible for including an instance of class
Catch
as a member variable of any
auto
class instance that may go out of scope as the result of an exception, and for defining the virtual function
destroyer()
to finalize class instances just as the class's destructor would.
The constructor for class
Catch
takes the address of the instance it is a member of (the
this
pointer) as its argument, and threads the instance on a linked list. When an exception occurs, the
destroyer()
function is applied to the instances on this list that will go out of scope as a result of the
longjmp()
to the exception handler.
The destructor for class
Catch
removes the instance from the linked list and applies
destroyer()
to it.
This solution is too difficult to use correctly, and the overhead of maintaining the linked list of
auto
class instances is too great to make this practical.
NIHCL_BADERRNUM
Go to the previous, next section.