There are situations in runtime, when program runs in exceptional way.
This could be handle in many different ways: using boolean return values,
assertions or exceptions.
Handling exceptional situations: teh old C way
struct record { ... };
record r;
extern int errno;
FILE *fp;
if ( (fp = fopen( "fname", "r")) != NULL )
{
fprintf( stderr, "can't open file %s\n", "fname");
errno = 1;
}
else if ( ! fseek( fp, 0L, n*sizeof(r)) )
{
fprintf( stderr, "can't find record %d\n", n);
errno = 2;
}
else if ( 1 != fread( &r, sizeof(r), 1, fp) )
{
fprintf( stderr, "can't read record\n");
errno = 3;
}
else ...
//============================================
//
// asserts
//
#include <cassert>
int main()
{
...
assert( ptr );
...
}
Goals of exception handling
- type-safe transmisson of arbitrary data from throw-point to handler
- no extra code/space/time penalty if not used
- every exceptionis cought by the appropriate handler
- groupping of exceptions
- ok in multithreaded environment
- cooperation with other languages (like C)
But the extra cost in code and runtime most cases unavoidable.
Scott Meyers states that declaring exceptions, even if it never
used costs extra 10% both in extra code and extra runtime.
Since PL/I programming languages have technique for handling exceptional
situations. Most famous is the setjmp/longjmp in C language. Here is an
example, how it is working.
#include <setjmp.h>
#include <stdio.h>
jmp_buf x;
void f()
{
longjmp(x,5);
}
int main()
{
int i = 0;
if ( (i = setjmp(x)) == 0 )
{
f();
}
else
{
switch( i )
{
case 1:
case 2:
default: fprintf( stdout, "error code = %d\n", i); break;
}
}
return 0;
}
Components of exception handling
Here we describe the main components of exceptions: the try block,
the catch handlers, and the throw expression.
The main components:
- try block
- catch handlers
- throw expression
try
{
f();
// ...
}
catch (T1 e1) { /* handler for T1 */ }
catch (T2 e2) { /* handler for T2 */ }
catch (T3 e3) { /* handler for T3 */ }
void f()
{
//...
T e;
throw e; /* throws exception of type T */
// or:
throw T(); /* throws default value of T */
}
A hadler H triggers on exception E if:
- H has same type as E
- H is unambigous base type of E
- H and E pointers or references and 1 or 2 holds
Exception hierarchies
In object-oriented programming languages the main technique for expressing
specialization and generalization is the inheritance hierarchy. Inheritance
in exceptions are particularry important for groupping exceptions in catch
handlers.
try {
...
}
catch( Der1 d1 ) { ... }
catch( Der2 d2 ) { ... }
catch( Der3 d3 ) { ... }
catch( Base b ) { ... }
// example1:
class net_error { ... };
class file_error { ... };
class nfs_error : public net_error, public file_error { ... };
void f()
{
try
{
...
}
catch( nfs_error nfs ) { ... }
catch( file_error fe ) { ... }
catch( net_error ne ) { ... }
}
// example2:
#ifndef MATRIX_H
#define MATRIX_H
#include <string>
#include <sstream>
#include <stdexcept>
struct matrixError
{
matrixError( std::string r) : reason(r) { }
const std::string reason;
};
struct indexError : public matrixError, public std::out_of_range
{
indexError( int i, const char *r="Bad index") :
matrixError(r), out_of_range(r), index(i) { }
const char *what() const throw()
{
std::ostringstream os;
os << std::out_of_range::what();
os << ", index = " << index;
return os.str().c_str();
}
virtual ~indexError() throw () { }
int index;
};
struct rowIndexError : public indexError
{
rowIndexError(int i) : indexError( i, "Bad row index") { }
};
struct colIndexError : public indexError
{
colIndexError(int i) : indexError( i, "Bad col index") { }
};
template <class T>
class matrix
{
public:
matrix( int i, int j );
matrix( const matrix &other);
~matrix();
matrix operator=( const matrix &other);
int rows() const { return x; }
int cols() const { return y; }
T& at( int i, int j) throw(indexError);
T at( int i, int j) const throw(indexError);
T& operator()( int i, int j);
T operator()( int i, int j) const;
matrix operator+=( const matrix &other);
private:
int x;
int y;
T *v;
void copy( const matrix &other);
void check( int i, int j) const throw(indexError);
};
template <class T>
matrix<T>::matrix( int i, int j)
{
x = i;
y = j;
v = new T[x*y];
}
template <class T>
matrix<T>::matrix( const matrix &other)
{
copy( other);
}
template <class T>
matrix<T>::~matrix()
{
delete [] v;
}
template <class T>
matrix<T> matrix<T>::operator=( const matrix &other)
{
if ( this != &other )
{
const int oldx = x;
const int oldy = y;
const T *oldv = v;
try {
copy( other);
delete [] oldv;
}
catch(...) {
x = oldx;
y = oldy;
delete [] v;
v = oldv;
throw;
}
}
return *this;
}
template <class T>
void matrix<T>::copy( const matrix &other)
{
x = other.x;
y = other.y;
v = 0;
v = new T[x*y];
for ( int i = 0; i < x*y; ++i )
v[i] = other.v[i];
// possible memory leak at v
}
template <class T>
void matrix<T>::check( int i, int j) const throw( indexError )
{
if ( i < 0 || i >= x )
throw rowIndexError(i);
if ( j < 0 || j >= y )
throw colIndexError(j);
}
template <class T>
T& matrix<T>::at( int i, int j) throw(indexError)
{
check(i,j);
return operator()(i,j);
}
template <class T>
T matrix<T>::at( int i, int j) const throw( indexError)
{
check(i,j);
return operator()(i,j);
}
template <class T>
T& matrix<T>::operator()( int i, int j)
{
return v[i*y + j];
}
template <class T>
T matrix<T>::operator() ( int i, int j) const
{
return v[i*y + j];
}
template <class T>
matrix<T> matrix<T>::operator+=( const matrix &other)
{
for ( int i = 0; i < x*y; ++i )
v[i] += other.v[i];
return *this;
}
template <class T>
matrix<T> operator+( const matrix<T> &left, const matrix<T> &right)
{
matrix<T> temp(left);
temp += right;
return temp;
}
#endif /* MATRIX_H */
Exceptions in the standard library
Comparing to Java language C++ has only a few standard exception types.
User programs can derive their own exception classes to express specific
exceptions. Here we present the standard exceptions.
class exception {}; // <exception>
class bad_alloc : public exception {}; // new <new>
class bad_cast : public exception {}; // dynamic_cast
class bad_typeid : public exception {}; // typeid(0)
class ios_base::failure : public exception {}; // unexpected() <ios>
class bad_exception : public exception {}; // unexpected()
class runtime_error : public exception {}; // math. computation
class range_error : public runtime_error {};
class overflow_error : public runtime_error {};
class underflow_error : public runtime_error {};
class logic_error : public exception {};
class domain_error : public logic_error {}; // domain error
class invalid_argument : public logic_error {}; // bitset char != 0 or 1
class length_error : public logic_error {}; // std::string length exceeded
class out_of_range : public logic_error {}; // bad index in cont. or string
namespace std
{
class exception
{
public:
virtual const char *what() const throw();
...
};
}