Staying Safe in C++

What is Exception Safety?

Exceptions are gross. One of the problems with exceptions is that, as they break out of the normal control flow, they can potentially leave our program in a bad state. How do we avoid this?

Well, we can guarantee that we never throw an exception, either because our code is perfect, or, we catch and smoothly handle all exceptions at the point they are thrown. This is a worthy goal, but might not be entirely practical. So the alternative is to follow the philosophy of exception safety. This is the design philosophy that says that we can throw exceptions, but, when we do, we must leave things in a good state.

There are two different flavours of exception safety, strong exception safety and weak exception safety. Strong exception safety is a guarantee that when an exception is thrown during some operation, the overall state will be back to what it was before the operation started. In other words, our methods are atomic or transactional. Weak exception safety is the guarantee that when we throw an exception we will leave things in a valid state. In particular, both forms of exception safety guarantee that we will not leak any resources.

Usually we manage state via classes, and that is also how we think about exception safety. How we make a class exception safe depends on the state that our class manages, and the functionality it exposes via it’s methods.

An Example of Exception Safety

Here’s an example from a job interview I did recently. Suppose we have two strongly exception safe classes: ResourceManager1 and ResourceManager2. In our code we want to define another class, which we, optimistically, name, SafeCopyingClass, like so:

class SafeCopyingClass
{
private:
   int a;
   ResourceManager1 b;
   ResourceManager2* c;
}

The question is, how can we implement a copy constructor for this class in an exception safe way?

We have to copy three member variables. We know that copying an integer like the member a should not throw. This means that we should leave the copy of a until the very end, that way if something goes wrong with the other two copies, we won’t have to roll it back. So, to start, we are going to have something like this:

SafeCopyingClass(const SafeCopyingClass& other)
{
   /// lots of copying logic that might throw
   a = other.a;
}

Now what about copying the members b and c? Suppose we naively copy the objects b and c as below.

SafeCopyingClass(const& SafeCopyingClass& other)
{
   b = ResourceManager1(other.b);
   c = new ResourceManager2(*other.c);
   a = other.a;
}

If the copying of other.b throws, then we are still in the original state so we can just let the exception escape. However, if that first copy succeeds, and then the copy of the RescourceManager2 object throws, then we are not in the original pre-copy state. Indeed, things are even worse than that, we cannot safely get back to the original state. Anything we do to try and restore the pre-copy state, would involve copying ResourceManager1 objects, which would potentially throw again.

So, instead, we are going to copy the ResourceManager2 object first. We are in luck here, because c is in fact a pointer, which is a primitive type, and, although copying the object it points to could throw, copying the pointer itself should not. This means that, we can split the operation into a copy that may throw but leaves our state unchanged, and an assignment that changes our state but should not throw. Concretely, we first copy the other.c object and store the result in a temporary pointer, and then separately assign value in that temporary point to c.

We do this copy of the underlying ResourceManager2 object first, if this throws, as we have not altered the state of the SafeCopyingClass object, we can let this exception escape. The assignment of the value in other.c to c, alters the state so it should go at the end, after all the code that may throw. So our copy constructor now looks like this:

SafeCopyingClass(const SafeCopyingClass& other)
{
   ResourceManager2* temp_c = new ResourceManager2(*other.c);
   // more copying logic that might throw
   c = temp_c;
   a = other.a;
}

Now we just have to deal with the copying of our ResourceManager1 object b. After creating the temporary pointer for our other.c but before we assign that pointer to c, we are still in the original state, so we can copy b and allow exceptions to escape, at this point, like so:

SafeCopyingClass(const SafeCopyingClass& other)
{
   ResourceManager2* temp_c = new ResourceManager2(*other.c);
   b = ResourceManager1(other.b);
   c = temp_c;
   a = other.a;
}

However, there is still one issue with our code. We have a potential resource leak, and that is not only bad practice, it is not exception safe. Suppose, when we copy the other.b object an exception is thrown. Then control will leave this copy constructor. However we created an object of type ResourceManager2. The pointer temp_c is lost, but the object will live on, so we are leaking resources.

So, if the copy of other.b throws, we need to delete the object pointed to by temp_c. To do this, we wrap the ResourceManager1 copy in a try catch block. Then if an exception is thrown we can clean things up and rethrow the exception. So, here his our finished copy constructor:

SafeCopyingClass(const& SafeCopyingClass other)
{
   ResourceManager2* temp_c = new ResourceManager2(other.c);
   try
   {
      b = ResourceManager1(other.b);
   } 
   catch(...)
   {
      delete temp_c;
      throw; 
   }
   c = temp_c;
   a = other.a;
}

What is the general strategy here? We split our copying into stuff that may throw and stuff that should not throw. We do everything that might throw before everything that will not throw. That makes any potential rollback a lot simpler. When we have a pointers, we should copy the underlying objects first and store the result in temporary pointers. After that, we do the rest of the potentially throwing copies, this is where we have to be aware of potential resource leaks and non trivial rollbacks.