Chris Gregory

Home Blog LinkedIn Github

Java to C++ - Rule of Three and the copy and swap idiom

5/8/2016

In C++ it is pretty hard to make exception safe code. Exceptions can come at basically any time. Every call to new can throw, every function call (or operator function!) can throw. To make good programs we have to figure out how to make code that won't fail in the face of exceptions.

The copy and swap idiom addresses this in copy constructors, and helps us to simplify code.

First lets start with a basic example of a class with a controlled pointer:

class SwapExample {
    int* my_int;
public:
    SwapExample(int i) : my_int(new int(i)) {}

    int get() const { return *my_int; }
    void set(int i) { *my_int = i; }

    ~SwapExample() { delete my_int; }
};

int main() {
    SwapExample se(3);
    SwapExample copy = se;
}

But when this program is ran, there is a double free. This is because we didn't properly follow the rule of three. When you make a destructor that frees a resource, you need to also make a copy constructor and copy assignment operator. Here is an example of this:

class SwapExample {
    int* my_int;
public:
    SwapExample(int i) : my_int(new int(i)) {}

    // copy constructor
    SwapExample(const SwapExample& other)
        : my_int(new int(*other.my_int)) {}

    // copy assignment operator
    SwapExample& operator=(const SwapExample& other) {
        delete my_int;
        my_int = new int(*other.my_int);
        return *this;
    }

    int get() const { return *my_int; }
    void set(int i) { *my_int = i; }

    ~SwapExample() { delete my_int; }
};

#include <assert.h>
int main() {
    SwapExample se(3);
    SwapExample copy = se;

    copy.set(5);
    assert(copy.get() == 5);
    assert(se.get() == 3);
}

This is a decent implementation, but it breaks the DRY principle of not repeating yourself. We repeat the delete my_int in the copy assignment operator and the destructor, and new int(*other.my_int) in the copy constructor and the copy assignment operator.

In addition, it is not exception safe because if the memory allocation fails in the copy constructor, my_int points to deallocated memory, but the user of this class might still use it.

If we instead copy the object, then swap in those new values, we would solve both the repetition problem and the exception unsafety.

class SwapExample {
    int* my_int;
public:
    SwapExample(int i) : my_int(new int(i)) {}

    // copy constructor
    SwapExample(const SwapExample& other)
        : my_int(new int(*other.my_int)) {}

    // copy assignment operator
    SwapExample& operator=(SwapExample copy) {
        swap(copy);
        return *this;
    }

    void swap(SwapExample& other) {
        // allow swap to work for built in types
        using std::swap;
        swap(my_int, other.my_int);
    }

    int get() const { return *my_int; }
    void set(int i) { *my_int = i; }

    ~SwapExample() { delete my_int; }
};

// custom overload of std::swap
namespace std {
void swap(SwapExample& first, SwapExample& second) {
    first.swap(second);
}
}
        

By having our copy assignment operator take the class by value, it allows some compiler optimizations and allows brevity in code. The following code has about the same semantic meaning, but has more boilerplate (that I underlined):

SwapExample& operator=(const SwapExample& other) {
    SwapExample copy(other);
    swap(copy);
    return *this;
}

In summary, using swap provides more functionality for your class (a way to swap instances), simplifies the copying of the class by removing redundant code, and is safer.