Abnormal events

In case the computation proceeds as required, everything is fine. But what should be done when a failure occurs due to memory exhaustion, arithmetical overflow or the violation of an assertion? Must we check for all possible errors or must we rely on separate exception handling mechanisms? In general, an abnormal event occurs when an operation cannot perform its desired computation. See slide 6-abnormal.

Abnormal events

Exceptions in C++

\zline{\fbox{{\em try \& catch}}}
  class Matherr { }
  class Overflow : public Matherr {}
  
  try {
  	f(); 
do some arithmetic
} catch (Overflow) {
// handle Overflow
} catch (Matherr) {
// handle non-Overflow Matherr
}

Assertions

\zline{\fbox{{\em throw}}}
  template< class T, class X >
  inline void Assert(T expr, X x) {
     if (!NDEBUG) if (!expr) throw x;
     }
  

slide: Abnormal events

When an abnormal event occurs, two possible courses of action can be taken. Either the operation can fail and raise an exception (thereby transferring the flow of control to an exception handler) or the operation can invoke a correction routine (that tries to correct the situation that caused the abnormal event) and continue the computation. The latter solution is called an intervention. In contrast to an exception, an intervention does not change the flow of control; it performs the correction and then resumes the operation.

Interventions are familiar from daily computing practice in the form of interrupts, such as are used to kill a process. Under Unix, an interrupt is implemented as a signal that may be associated with a handler. The interrupt handler usually invokes the exit function.

Exceptions, on the other hand, are not (yet) familiar to most programmers and are only supported by a few languages.

One of the languages supporting exceptions is Eiffel. In Eiffel, exceptions are closely linked with the notion of contracts. Abnormal events occur whenever a contract is violated, that is when either a pre-condition, post-condition or invariant is not satisfied. For each of these cases an exception handler may be specified in the class defining the object for which an exception is raised. The way in which abnormal events are dealt with in Eiffel conforms to what is called the resumption model in  [BuhrMac92]. The resumption model allows a handler invoked for an exception to resume the computation after correcting the faulty situation. In Eiffel, the retry statement may be used to try the operation that resulted in raising an exception again.

Exceptions are part of the language definition of C++, but not many of the available compilers provide support for exceptions. The declaration of exceptions conforms to the definition of classes. For example, slide 6-abnormal declares a Matherr and an Overflow exception, which is publicly derived from Matherr. Obviously, the advantage of employing the class mechanism is that inheritance may be used to organize exceptions in groups. The most specific exception may then be tested for first, as in the catch statements following the try block in slide 6-abnormal. As the example shows, exception handling is activated by wrapping a group of statements or function calls in a try block. Whenever a failure occurs, the exception handlers declared by the subsequent catch statements will be tried to do something about it.

An exception may be raised by means of a throw statement as illustrated for the Assert function. The Assert function mimics the behavior of the assert macro, but in addition allows us to raise an explicit exception such as BadArgs or Invariant (provided these are defined as exceptions).

In contrast to Eiffel, which allows for resuming the computation after the occurrence of an exception, C++ supports only termination semantics for exceptions, which results in terminating the function call that caused the exception. The main reason to adopt termination semantics is (according to  [St94]) that resumption semantics invites programmers to employ exceptions for things other than that for which they are meant, such as debugging and the like, or allocating new resources. However, such tasks are often better performed by an explicit handler function as may, for example, be defined to handle allocation problems when calling the new operator. Exceptions, on the other hand, are primarily meant to be used to handle errors for which no such remedies exist.

Lacking adequate language support, both exceptions and interventions may be simulated, respectively by setting a global flag which is tested after the operation and by adding intervention routine parameters to function definitions. However, such simulations are tedious and error-prone to implement and, moreover, violate encapsulation and endanger extensibility.