A lot of work, sweat and saliva was dedicated to discover and describe programming idioms to ease the work of writing exception-safe code. Most texts (the interested reader will find useful references in the section called “References”) which deal with this problem distinguish between:
Strong exception-safety guarantee: functions ensure that the state of the program is atomically changed with regard to exceptions. That is, if an exception arises at any point, the functions guarantee that the program state is not changed.
Weak (or Basic) exception-safety guarantee: functions ensure that the program does not become corrupted after an exception is thrown. The state of some objects might have changed but no memory is to be leaked and no object should be in a corrupted or inconsistant state.
The focus is on writing all the code which lies between the throw and the corresponding catch statement. As you probably have noticed it, the same problem exists in code with uses exceptions and code which does not uses them. In both cases, the programmer must be careful to free all localy allocated ressources when the error code path is taken which is always a chore and error-prone.
What I have always found fascinating and rather surprising is that all the techniques developed to improve the look of code which must provide exception safety can be applied as-is to code which does not use exceptions to propagate program errors. The main technique used by exception safety code is to build upon one of the properties of the exception specification: all automatic variables locally allocated are destroyed upon stack unwinding. While this property can be seen by most readers as the source of all problems (pointers are destroyed but not the data they point to), it is paradoxically also the source of the solution.
The solution lies in what everyone calls smart pointers: while wide use of smart pointers makes templates mandatory, it is possible to understand the basic mechanism of these small object classes without using any templates.
A smart pointer is a small class which is used to hold a pointer to another object:
class Error {};
class Foo
{
public:
void Action (void) {throw Error ();}
};
class FooPtr
{
public:
FooPtr (Foo *ptr) {m_ptr = ptr;}
~FooPtr () {delete m_ptr;}
Foo *GetFoo (void) {return m_ptr;}
private:
Foo *m_ptr;
};
static void dumb (void)
{
Foo *ptr = new Foo ();
try {
ptr->Action ();
} catch (Error &e) {
delete ptr;
}
}
static void smart (void)
{
FooPtr ptr (new Foo ());
ptr.GetFoo ()->Action ();
// FooPtr's destructor is invoked upon exit of the function code block.
// it destroys automatically the object it points to.
// If an exception arises, FooPtr's destructor is also invoked because it
// is an automatic local variable.
}
int main (int argc, char *argv[])
{
dumb ();
try {
smart ();
} catch (Error &e) {
// handle.
}
}
The key point to notice here is that the FooPtr class is used to associate a destructor to a pointer to the Foo class which is thus automatically invoked by the stack unwinding code during exception propagation.
Of course, the above-described smart pointer class can be rewritten to the following less easy to understand code which has the nice side-effect to allow for much nicer-looking client code:
class Error {};
class Foo
{
public:
void Action (void) {throw Error ();}
};
class FooPtr
{
public:
FooPtr (Foo *ptr) {m_ptr = ptr;}
~FooPtr () {delete m_ptr;}
Foo *operator-> () {return m_ptr;}
private:
Foo *m_ptr;
};
static void smart (void)
{
FooPtr ptr (new Foo ());
ptr->Action ();
// FooPtr's destructor is invoked upon exit of the function code block.
// it destroys automatically the object it points to.
// If an exception arises, FooPtr's destructor is also invoked because it
// is an automatic local variable.
}
int main (int argc, char *argv[])
{
try {
smart ();
} catch (Error &e) {
// handle.
}
}
Here, the call to GetFoo () is replaced with the operator ->. If you really want to design a nice smart pointer class, it will clearly require a bit more code and thinking (look at the template class auto_ptr in the C++ standard library) but the basic idea will stay the same.
As I mentioned earlier, the idea of smart pointers can be used to rewrite the error-propagation code we wrote in the section called “What is an exception ?” (if you write smart pointer classes which override the == and -> operators):
// no exceptions
typedef enum {
ok,
error,
error_alloc
} ErrorStatus;
class Foo
{
public:
ErrorStatus Action (void) {return ok;}
};
class FooPtr
{
public:
FooPtr (Foo *ptr) {m_ptr = ptr;}
~FooPtr () {delete m_ptr;}
Foo *operator-> () {return m_ptr;}
bool operator == (void * v) {return m_ptr == v;}
private:
Foo *m_ptr;
};
class Bar
{
public:
ErrorStatus Action (void) {return ok;}
};
class BarPtr
{
public:
BarPtr (Bar *ptr) {m_ptr = ptr;}
~BarPtr () {delete m_ptr;}
Bar *operator-> () {return m_ptr;}
bool operator == (void * v) {return m_ptr == v;}
private:
Bar *m_ptr;
};
ErrorStatus no_exceptions (void)
{
FooPtr foo (new Foo ());
if (foo == 0) {
return error_alloc;
}
ErrorStatus status = foo->Action ();
if (status != ok) {
return status;
}
BarPtr bar (new Bar ());
if (bar == 0) {
return error_alloc;
}
status = bar->Action ();
if (status != ok) {
return status;
}
out:
return status;
}
int main (int argc, char *argv[])
{
ErrorStatus status;
status = no_exceptions ();
if (status != ok) {
}
}
And the exception version:
// exceptions
class Error {};
class Foo
{
public:
void Action (void) {throw Error ();}
};
class FooPtr
{
public:
FooPtr (Foo *ptr) {m_ptr = ptr;}
~FooPtr () {delete m_ptr;}
Foo *operator-> () {return m_ptr;}
bool operator == (void * v) {return m_ptr == v;}
private:
Foo *m_ptr;
};
class Bar
{
public:
void Action (void) {throw Error ();}
};
class BarPtr
{
public:
BarPtr (Bar *ptr) {m_ptr = ptr;}
~BarPtr () {delete m_ptr;}
Bar *operator-> () {return m_ptr;}
bool operator == (void * v) {return m_ptr == v;}
private:
Bar *m_ptr;
};
void exceptions (void)
{
FooPtr foo (new Foo ());
foo->Action ();
BarPtr bar (new Bar ());
bar->Action ();
}
int main (int argc, char *argv[])
{
try {
exceptions ();
} catch (Error &e) {
}
}
Here, admitedly, the exception version of the code looks, erm, a bit more simple than the error-code based version of the code which is, at best, clutered with if statements (but it's clearly much nicer code since you do not have to worry about freeing the objects correctly in each different if case: it is automatically handled by the compiler).