Exception safety

Standard library functions often invoke operations that a user supplies as function or template arguments. Some of these operations will occasionally throw exceptions. Other functions, like allocator functions can also throw exceptions. We must program in exception safe way to survive.


// Exceptions and the evaluation order:

void f( int expr1, int expr2);
int  g( int expr);
int  h( int expr);

f( g( expr1), h(expr2) )    // what is the evaluation order?


In the real word:

template <class T>
void f( T1*, T2* );



f( new T1, new T2);  // what is the evaluation order of constructors?


The process of new expression:
1. It allocates memory
2. Constructs the object at that memory
3. If construction fails, memory will be freed
4. Otherwise returns with pointer to the memory


f( new T1, new T2);  // what is the evaluation order of constructors?

A possible scenario:
1. Allocates memory for T1
2. Constructs the object T1
3. Allocates memory for T2
4. Constructs the object T2
5. Call f()

If 3. or 4. fails destructor of T1 won't be executed -> memory leak

Another possible scenario:
1. Allocates memory for T1
2. Allocates memory for T2
3. Constructs the object T1
4. Constructs the object T2
5. Call f()

If 3. fails then T1 is deallocated, but T2 is not -> memory leak
If 4. fails then T2 is deallocated, but T1 is not -> memory leak


f( auto_ptr<T1>, auto_ptr<T2>);

f( auto_ptr<T1>(new T1), auto_ptr<T2>(new T2));


A possible scenario:
1. Allocates memory for T1
2. Constructs the object T1
3. Allocates memory for T2
4. Constructs the object T2
5. Constructs auto_ptr<T1>
6. Constructs auto_ptr<T2>
7. Call f()

Same problems...


Solution:

f( auto_ptr<T1>, auto_ptr<T2>);

auto_ptr<T1> t1(new T1);
auto_ptr<T2> t2(new T2);
f( t1, t2);


//==============================================


Strong exception safety:


// dangerous version:
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];
}


// exception safe version:
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;
}


//==============================================

Moral:

#1 : Constructor function try block good only for translating exception
#2 : Destructor function try block is good for almost nothing
#3 : Other functions try block are equivalent with try block inside
the function body.
#4 : Do unmanaged resource allocation in constructor body,
never in the initializer list (or more better: use resource objects)
#5 : Clean up unmanaged resources in local try blocks
#6 : Exception specification of a constructor must be the union
#7 : Use Pimpl idiom if a base object can throw but you want survive
#8 : Prefer "resource allocation is initialization"
#9 : Perform every explicite resource allocation in its own statement


//===============================================


Exception safety in the standard library


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
}


Theoretically there is two possibility:
- No guarantees: the container possibly corrupted
- Strong guarantees: all object will keep the invariant


In practice in the standard library:
- Basic guarantee for all operations:
basic invariants are kept, no memory leak or other resource problem
- Strong guarantee for key operations:
the operation succeeds or has no effect. This guarantee is provided
for some key operations, like push_back(), insert() on a list, etc...
- Nothrow guarantees for some operation:
some operations are guaranteed not to throw exception, like pop_back()
or swap().

Basic and Strong guarantee suppose, that the user operations do not leave
the container operations in invalid state.


Guarantees for vector and deque:

- push_back(), push_front(): strong guarantee.
- insert(): strong, unless copy constructor or assignment of user class throws
- erase(): not throws, unless copy constr. or assignment of user class does it
- pop_back(), pop_front(): nothrows

Guarantees for list:

- insert(), push_back(), push_front(): strong guarantee.
- erase(), pop_back(), pop_front(), splice(), reverse(): nothrows
- remove(), remove_if(), unique(), sort(), merge(): not throws, unless
the user class predicate or comparison function does it.

Guarantees for associative containers:

- insert(): strong guarantee.
- erase(): nothrows.


Table:
        vector          deque           list            map

clear()         nothrow(copy)   nothrow(copy)   nothrow         nothrow

erase()         nothrow(copy)   nothrow(copy)   nothrow         nothrow

1-elem insert() strong(copy)    strong(copy)    strong          strong

N-elem insert() strong(copy)    strong(copy)    strong          basic

merge()         --              --              nothrow(comp)   --

push_back()     strong          strong          strong          --

push_front()    --              strong          strong          --

pop_back()      nothrow         nothrow         nothrow         --

pop_front()     --              nothrow         nothrow         --

remove()        --              --              nothrow(comp)   --

remove_if()     --              --              nothrow(pred)   --

reverse()       --              --              nothrow         --

splice()        --              --              nothrow         --

swap()          nothrow         nothrow         nothrow         nothrow
                                                        (copy-of-comp)
unique()        --              --              nothrow(comp)   --


std::string: basic_string<Ch, char_traits<Ch>, A>

- The char_traits from std does not throw exceptions: basic guarantee
- erase(), insert(), push_back() and swap(): strong guara