Exceptions and Error Handling
Reading: Dietel sections 13.1 - 13.12
Local Error Handling
- What we have used up until now
- If an error occurs, signal it and handle it at that point
if ( ! ( ptr = new int [ size ] ) )
cout << "Error: out of memory!" << endl; exit ( 1 );- Fine if one programmer writes entire program
- Want to be able to separate error detection (writer of class) from error correction/handling (user of class)
- Users of a class can handle errors, but not detect them (without knowing class details)
- Writers of a class can detect errors, but don't know how to handle them in the scope of a larger program
Exceptions
- Any class (or fundamental type) can be used as an exception
- Exception instances are passed between error detector and handler
- We throw an exception when an error is detected; we catch it when it can be handled
- Localized, separate code for detection, correction
- Different users may handle thrown exceptions differently
Example: Rational number arithmetic
class Rational
{
public: // Also accessors, mutators, etc.
Rational (int numerator = 0, int denominator = 1);
Rational operator + (const Rational& lhs) const;
Rational operator - (const Rational& lhs) const;
Rational operator * (const Rational& lhs) const;
Rational operator / (const Rational& lhs) const;
private:
int numerator;
int denominator;
};
class DivideByZero
// Use only to signal an error condition
{
public:
DivideByZero ( Rational lhs );
Rational GetOperand ( );
private:
Rational lhs;
};
Rational Rational :: operator / ( const
Rational& rhs )
{
Rational result;
if ( rhs.numerator == 0 )
throw DivideByZero ( *this );
else
return Rational ( numerator * rhs.denominator, denominator *
rhs.numerator);
}
Rational DivideByZero :: GetOperand ( )
{
return lhs;
}
void foo (Rational a, Rational b )
{
Rational c;
try
{
c = a / b;
}
catch ( DivideByZero& dbz ) // Catching by
reference is preferred
// Otherwise exception's copy constructor used
{
cout << "Tried to divide
" << dbz.GetOperand ( ) << "by zero!" << endl;
c = 0;
}
// whatever else...
}
foo can't detect the divide-by-zero; operator's implementation is hidden
operator can't handle the divide-by-zero; may be critical, or may be ignored
Behavior of throw / catch blocks
- If an exception is thrown within a try block, control passes to following catch block(s) (remainder of try is not executed)
- catch blocks are examined in order until one matching the exception is found (if any)
- If a matching catch is found, that block executes, and control passes to after try / catch
What if the catch block can't handle an exception?
- If no matching catch is found, or if an exception is thrown from a function call outside a try block, control passes up the call stack
- The hope is that a (direct or indirect) calling function knows how to handle the error
- This ensures that occurrences of errors are not ignored (as when errors are signaled with special return values, state variables)
- Call stack is unwound one function at a time until the error is caught (or the stack unwinds completely)
- As call stack unwinds, destructors called for all objects in broken function calls; destructors can enforce (local) cleanup rules
If such a destructor throws something, program terminates.- This means we can't go back down the call stack; execution that originally generated the (uncaught) exception can't be resumed
- Once a catch block is completed, thrown object's destructor called
Try / catch blocks can be nested
Partial Error Handling
- Sometimes a catch block performs checks and finds it can't handle the error
- Exception can be passed on up the chain
maybe catch must do some cleanup as well -- free memory, release a resource, etc.- Within a catch, throw with no operand re-throws the exception which led to the catch being executed
- Can use to distribute the recovery effort among multiple functions
- Can catch anything thrown by writing
catch ( ... )- This can be used to perform general cleanup before passing exception on up the chain
- Best to have object destructors do most of the cleanup (called automatically)
- Class auto_ptr added to standard C++ to provide object functionality for pointers
Specifying a Function's Exception Throws
- The exceptions a function throws affects behavior of calling functions
- Function prototype can specify the exceptions a function throws
- Calling function knows what to expect and what to handle
- void foo ( int a, float b ) throw ( ExcClass1, ExcClass2 );
- This function may only throw ExcClass1 and ExcClass2 (and derived classes of these)
- If no specification, function may throw any exception
- throw ( ) specifies no exceptions possible
- Runtime mismatch: std :: unexpected ( ) called (usually terminates); useful in debugging