Cserép Máté
I am a researcher and lecturer in computer science with several years of experience in object-oriented design and development. I have an application development experience primarily in C++, the C#/.NET technologies, PHP and Python.
Máté Cserép
October 2015, Budapest
There are situations in runtime, where program runs in exceptional way. This could be handle in many different ways: using boolean return values, assertions or exceptions. The 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 {
/* ... */
}
#include <cassert>
struct record { /* ... */ };
int main()
{
/* ... */
record *ptr = /* ... */
assert(ptr);
/* ... */
}
type-safe transmission of arbitrary data from throw point to handler point
no extra code/space/time penalty if not used
every exception is caught by the appropriate handler
groupping of exceptions
multithread environment support
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.
jmp_buf x;
void f()
{
longjmp(x,5);
}
Since PL/I programming languages have techniques for handling exceptional situations. Between the archaic ones the most famous is the setjmp/longjmp in the C language.
int i = 0;
if ((i = setjmp(x)) == 0)
{
f();
}
else
{
switch(i)
{
case 1: /* ... */ break;
case 2: /* ... */ break;
default: fprintf(stdout, "error code = %d\n", i); break;
}
}
void f()
{
/* ... */
T1 e;
throw e; /* throws exception of type T */
// alternatively:
throw T1(); /* throws default value of T */
}
A handler H triggers on exception E if:
try
{
f();
/* ... */
}
catch (T1 e1) { /* handler for T1 */ }
catch (T2 e2) { /* handler for T2 */ }
catch (T3 e3) { /* handler for T3 */ }
Throw:
Catch:
In object-oriented programming languages the main technique for expressing specialization and generalization is the inheritance hierarchy. Inheritance in exceptions are particularly important for grouping exceptions in catch handlers.
try {
/* ... */
}
catch(Derived1 d1) { /* ... */ }
catch(Derived2 d2) { /* ... */ }
catch(Derived3 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 e) { /* ... */ }
catch(file_error e) { /* ... */ }
catch(net_error e) { /* ... */ }
}
Example:
Usage:
class exception {}; // <exception>
class bad_cast : public exception {}; // dynamic_cast
class bad_typeid : public exception {}; // e.g. typeid(0)
class bad_exception : public exception {}; // unexpected()
class bad_weak_ptr : public exception {}; // weak_ptr -> shared_ptr (C+11)
class bad_function_call : public exception {}; // std::function::operator() (C+11)
class bad_alloc : public exception {}; // new
class bad_array_new_length : public bad_alloc {} // new T[-1] (C+11)
class runtime_error : public exception {}; // errors that can only be detected during runtime
class range_error : public runtime_error {}; // e.g. floating point ovf or unf
class overflow_error : public runtime_error {}; // e.g. integer overflow INT_MAX+1
class underflow_error : public runtime_error {}; // e.g. integer underflow INT_MIN-1
class system_error : public runtime_error {}; // e.g. std::thread constr.
class logic_error : public exception {}; // errors that could be prevented before the execution
class domain_error : public logic_error {}; // e.g. std::sqrt(-1)
class invalid_argument : public logic_error {}; // e.g. bitset char != 0 or 1
class length_error : public logic_error {}; // e.g. str.resize(-1)
class out_of_range : public logic_error {}; // e.g. bad index in cont. or string
class future_error : public logic_error {}; // e.g. duplicate get/set (C++11)
class ios_base::failure : public exception {}; // base class for I/O exceptions (before C+11)
class ios_base::failure : public system_error {}; // base class for I/O exceptions (C+11)
Comparing to e.g. the Java language C++ has only a few standard exception class.
namespace std
{
class exception
{
public:
exception();
exception(const exception& other);
virtual ~exception();
exception& operator=(const exception& other);
virtual const char *what() const;
private:
/* ... */
};
}
Definition of std::exception
class X;
int main()
{
try
{
X *xp = new X();
}
catch(...) { /* space allocated, but xp was not set */ }
// still no problem: system deallocates space
}
Constructors have no return value. Still, a constructor sometimes must propagate failure. There are several method for this: error flags, globals, and most importantly exceptions. However exceptions in constructors have special behaviour.
class X
{
public:
X(int i) { p = new char[i]; init(); }
~X() { delete[] p; } // must not throw exception
private:
void init() { /*... throw ... */ } // BAD: destructor won't run!
char *p; // constructor was not completed
};
However:
class X
{
public:
X(int i, int j) : y(i), z(j) { } // y(i) or z(j) throws exception
// but X must not emit any
private:
Y y;
Z z;
};
class X
{
public:
X(int i, int j) try : y(i), z(j) // y(i) or z(j) throws exception
{ }
catch(...)
{
// we get here if either y(i) or z(j) throws exception
// if y(i) throws, then z uninitialized
// if z(j) throws, then ~Y::Y() has already executed
// nothing to do here ...
}
private:
Y y;
Z z;
};
If a sub-object initializer throws exception then sub-object has not been created: no object without sub-object, so constructor will throw exception!
#include <iostream>
class X
{
public:
X() { throw 1; }
};
class Y
{
public:
Y() try: x() { }
catch(...) { /* throw; */ }
private:
X x;
};
int main() try
{
Y y;
return 0;
}
catch (int i)
{
std::cerr << "exception: " << i << std::endl;
}
Destructor can be called in one of two ways:
X::~X() throw()
try
{
/* ... */
}
catch(...) { /* ... */ }
The rule of thumb for exceptions in destructor:
They must never throw
in the later emitting an exception cause undefined behaviour,
most likely terminate()
X::~X()
{
if (!uncaught_exception()) {
// code that could throw...
}
else {
// code must not throw
}
}
struct BaseException
{
virtual void what() { std::cout << "BaseException" << std::endl; }
};
struct DerivedException : BaseException
{
void what() { std::cout << "DerivedException" << std::endl; }
};
void test()
{
throw DerivedException();
}
int main()
{
try
{
test();
}
catch(BaseException ex)
{
ex.what();
}
return 0;
}
Catching exceptions by value is a bad idea, object slicing would occur.
struct BaseException
{
virtual void what() { std::cout << "BaseException" << std::endl; }
};
struct DerivedException : BaseException
{
void what() { std::cout << "DerivedException" << std::endl; }
};
void test()
{
throw DerivedException();
}
int main()
{
try
{
test();
}
catch(BaseException &ex)
{
ex.what();
}
return 0;
}
Throwing pointers is also bad in most cases, because of memory management.
There are several resource allocation problem connecting to exception handling. These are mostly the result of the procedural usage of resource allocation and free.
void f()
{
char *ptr = new char[1024];
g(ptr); // can throw exception
h(ptr);
delete [] ptr;
}
void f()
{
try
{
char *ptr = new char[1024];
g(ptr);
h(ptr);
delete [] ptr;
}
catch(...)
{
delete [] ptr;
throw; // rethrow original exception
}
}
One technique to solve the problem is what Stroustrup called: Resource Allocation Is Initialization
template <typename T>
class Res
{
public:
Res(int i) : p( new T[i] ) {}
~Res() { delete [] p; }
T* ptr() { return p; }
private:
T* p;
};
void f()
{
Res r(1024);
g(r.ptr); // can throw exception
h(r.ptr);
}
RAII, also called Resource Acquisition Is Initialization
C++ does not support finally blocks. C++ instead supports RAII.
void f( int expr1, int expr2);
int g( int expr);
int h( int expr);
f( g( expr1), h(expr2) );
What will be the evaluation order of the last expression?
Evaluation order of a constructor:
template <class T>
void f( T1*, T2* );
f( new T1, new T2);
What will be the evaluation order of the constructors?
template <class T1, class T2>
void f( T1*, T2* );
f( new T1, new T2);
What will be the evaluation order constructors?
A possible scenario:
Another possible scenario:
If 3. or 4. fails destructor of T1 won't be executed, which is a memory leak
If 3. fails then T1 is deallocated, but T2 is not, which is a memory leak
If 4. fails then T2 is deallocated, but T1 is not, which is a memory leak
template <class T1, class T2>
f( auto_ptr<T1>, auto_ptr<T2>);
f( auto_ptr<T1>(new T1), auto_ptr<T2>(new T2));
What will be the evaluation order constructors?
A possible scenario:
Same problems.
template <class T1, class T2>
f( auto_ptr<T1>, auto_ptr<T2>);
auto_ptr<T1> t1(new T1);
auto_ptr<T2> t2(new T2);
f(t1, t2);
Solution:
template <class T>
matrix<T> matrix<T>::operator=( const matrix &other)
{
if ( this != &other )
{
delete [] v;
copy( other);
}
return *this;
}
template <class T>
void matrix<T>::copy( const matrix &other)
{
x = other.x;
y = other.y;
v = new T[x*y];
for ( int i = 0; i < x*y; ++i )
v[i] = other.v[i];
}
template <class T>
matrix<T> matrix<T>::operator=( const matrix &other)
{
if ( this != &other )
{
matrix temp(other);
T *oldv = v;
// swap() in STL
x = temp.x;
y = temp.y;
v = temp.v;
temp.v = oldv;
}
return *this;
}
Dangerous version
Exception safe version
In Java exception specification is a mandatory part of method declaration. In C++ exception specification is optional - and deprecated since C++11. When used, the behaviour is different.
class E1;
class E2;
void f() throw(E1) // throws only E1 or subclasses
{
/* ... */
throw E1(); // throws exception of type E1
/* ... */
throw E2(); // calls unexpected() which most likely calls terminate()
}
void f() try
{
/* ... */
}
catch(E1) { throw; }
catch(...) { std::unexpected(); }
Alternatively:
In Java exception specification is a mandatory part of method declaration. In C++ exception specification is optional - and deprecated since C++11. When used, the behaviour is different.
class E1;
class E2;
void f() throw(E1, std::bad_exception) // throws only E1 or subclasses
{ // or bad_exception
/* ... */
throw E1(); // throws exception of type E1
/* ... */
throw E2(); // calls unexpected() which throws bad_exception
}
typedef void (*unexpected_handler)();
unexpected_handler set_unexpected(unexpected_handler);
typedef void (*terminate_handler)();
terminate_handler set_terminate(terminate_handler);
Unexpected and terminate handlers:
Note: terminate() by default calls abort()
bool noexept(expr);
void f() noexcept(expr) { }
void f() noexcept(true) { }
void f() noexcept { }
noexcept was added rather than just removing all throw specifiers other than throw() because noexcept is more powerful. noexcept can have a parameter which resolves into a boolean. If true, then the noexcept sticks. If the boolean is false, then the noexcept doesn't stick and the function may throw.
Can improve efficiency:
void f() throw() { }
void fun1() noexcept
{
int i = 0; // noexcept
int * p = &i; // noexcept
p = nullptr; // noexcept
}
void fun2() noexcept
{
fun1();
fun1();
}
template <typename T>
void f() noexcept(noexcept(T::g()))
{
g();
}
void pair::swap(pair& p)
noexcept(noexcept(swap(first, p.first)) &&
noexcept(swap(second, p.second)));
void f(vector<X>& v, cosnt X& g)
{
v[2] = g; // X::operator= might throw exception
v.push_back(g); // vector<X> 's allocator might throw exception
sort(v.begin(), v.end()); // X's less than might throw exception
vector<X> u = v; // X's copy constructor might throw exception
}
Exceptions may occur in several cases with the STL:
Theoretically there are two possibilities:
In practice in the standard library all the operations are categorized by the following:
Note: basic and strong guarantee suppose, that the user operations do not leave the container operations in invalid state.
Guarantees for vector and deque:
Guarantees for list:
Guarantees for associative containers:
Guarantees for string:
By Cserép Máté
I am a researcher and lecturer in computer science with several years of experience in object-oriented design and development. I have an application development experience primarily in C++, the C#/.NET technologies, PHP and Python.