Exception Safety

Writing code that may be interrupted by an exception is not easy and takes practice. Even the simplest code must be thought through to insure that it works properly in the event that an exception is thrown while it executes ("exception safe"). This is especially true when writing member functions of an object that change the object (mutators, assignment operator and constructors -- we need to insure that the object does not become a "zombie".

A Smart Array Example

Consider the class below that implements a variable size array of dynamically allocated Fred objects. class FredArray { public: // constructors FredArray ( int size = 100 ); // assignment operator FredArray operator=( const FredArray& rhs); // other public methods private: int m_size; // the number of Freds in the array Fred *m_data; // a pointer to the array of Freds }; // unsafe assignment operator implementation FredArray& FredArray::operator=( const FredArray& rhs) { if ( this != &rhs ) { // free existing Freds delete [] m_data; // 1 // now make a deep copy of the right-hand object m_size = rhs.m_size; // 2 m_data = new Fred [ m_size ]; // 3 for (int j = 0; j < m_size; j++ ) // 4 m_data[ j ] = rhs.m_data [ j ]; // 5 } return *this; } What's the problem with relatively simple this code? It's not "exception safe". Code which is exception-safe guards against leaving an object (or program) in an invalid or inconsistent state if an exception should occur. Let's examine the code line by line

Now consider the state of the FredArray object if either line 3 or line 5 throws an exception. The old array has been deleted (line 1), the size has been changed (line 2), but not all of the Freds have been copied.

We address these issues by carefully writing the code so that any exception that might be thrown does not cause the FredArray to take on an invalid state. In Herb Sutter's book Exceptional C++, he puts it something like this - first do anything that would cause an exception "off to the side" using local variables so as not to change the state. When the "real work" has been accomplished successfully, then use operations that don't throw exceptions to actually change your program/object state.

// Better assignment operator implementation FredArray& FredArray::operator=( const FredArray& rhs) { if ( this != &rhs ) { // code that may throw an exception first // make a local temporary deep copy Fred *tempArray = new Fred[rhs.m_size]; for ( int j = 0; j < rhs.m_size; j++ ) tempArray[ j ] = rhs.m_data [ j ]; // now code that does not throw exceptions delete [] m_data; m_size = rhs.m_size; m_data = tempArray; } return *this; }

Exception Guarantees

There are three generally accepted classifications of exception-safe guarantees that a function can make in increasing order of "strictness". We should make sure that our code falls into one of these categories.
  1. Weak (Basic) Guarantee -- the function ensures that the program/object does not become corrupt. No resource (memory) is leaked and the object remains in a usable, destructible state.
  2. Strong Guarantee -- the function ensures that if an exception is thrown, the function has no effect. The program/object state is the same as it was before the function was thrown.
  3. NoThrow Guarantee -- the function ensures that it will not throw an exception under any circumstances. Destructors, delete and delete[ ] should provide the NoThrow Guarantee.
It's sometimes said that the Basic/Weak and Strong guarantees represent the two levels of exception safety. The purpose of the NoThrow guarantee is to enable the Basic/Weak and Strong guarantees to be honored.

Which guarantee should our code support? The C++ standard library follows this guideline

A function should always support the strictest guarantee that it can support without penalizing users who don't need it What do we mean by "penalizing" your user? Adding additional overhead in terms of execution time and/or space. Providing the Strong Guarantee often (not always) requires a trade-off in performance.

For example, consider a function that inserts a item into the middle of an array or vector. To provide the Strong Guarantee, this code would have to make a copy of the original array/vector before attempting the insertion, a clear impact on the performance of the code.

Dennis Frey
Last modified: Fri Nov 19 11:54:46 EST 2004