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: the old C way
==============================================
struct record { ... };
struct record rec;
extern int errno;
int myerrno;
FILE *fp;
if ( NULL == (fp = fopen( "fname", "r")) )
{
fprintf( stderr, "can't open file %s\n", "fname");
fprintf( stderr, "reason: %s\n", strerror(errno));
myerrno = 1;
}
else if ( ! fseek( fp, n*sizeof(rec), SEEK_SET) )
{
fprintf( stderr, "can't find record %d\n", n);
myerrno = 2;
}
else if ( 1 != fread( &r, sizeof(r), 1, fp) )
{
fprintf( stderr, "can't read record\n");
myerrno = 3;
}
else
{
...
}
Handling exceptional situations: Asserts
========================================
#include <cassert>
int main()
{
...
assert( ptr );
...
}
#include <type_traits>
template <typename T>
void swap(T &x, T &y)
{
static_assert( std::is_copy_constructible<T>::value, "Swap requires copying" );
static_assert( std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_move_assignable<T>::value, "Swap may throw" );
auto tmp = x;
x = y;
y = tmp;
}
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) { }
catch (T2 e2) { }
catch (T3 e3) { }
void f()
{
T e;
throw e;
throw 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 ) { ... }
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 ) { ... }
}
#ifndef MATRIX_H
#define MATRIX_H
#include <string>
#include <sstream>
#include <stdexcept>
struct matrixError
{
matrixError( std::string r) : reason(r) { }
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)
{
std::ostringstream os;
os << index;
reason += ", index = ";
reason += os.str();
}
const char *what() const throw()
{
return reason.c_str();
}
virtual ~indexError() nothrow;
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);
T at( int i, int j) const;
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;
};
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];
}
template <class T>
void matrix<T>::check( int i, int j) const
{
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)
{
check(i,j);
return operator()(i,j);
}
template <class T>
T matrix<T>::at( int i, int j) const
{
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];
}
#endif
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 {};
class bad_cast : public exception {};
class bad_typeid : public exception {};
class bad_exception : public exception {};
class bad_weak_ptr : public exception {};
class bad_function_call : public exception {};
class bad_alloc : public exception {};
class bad_array_new_length : bad_alloc {}
class runtime_error : public exception {};
class range_error : public runtime_error {};
class overflow_error : public runtime_error {};
class underflow_error : public runtime_error {};
class system_error : public runtime_error {};
class ios_base::failure : public system_error {};
class logic_error : public exception {};
class domain_error : public logic_error {};
class invalid_argument : public logic_error {};
class length_error : public logic_error {};
class out_of_range : public logic_error {};
class future_error : public logic_error {};
namespace std
{
class exception
{
public:
exception();
exception( const exception& other);
virtual ~exception();
exception& operator=( const exception& other);
virtual const char *what() const;
private:
};
}