Copy and Swap Idiom

I learned a new trick today, while reading about C++11 (of which more to come). It’s a technique I wish I’d known about a long time ago, as it addresses an issue I’ve always thought C++ had – forcing you to duplicate code. That issue is addressed even more thoroughly with C++11, which has introduced the ability for one constructor to call another (about time on that one).

The trick I learned is called the copy and swap idiom; and I thought I’d go through it bit by bit in case it’s news to anyone else.

So… you’re writing a C++ object to manage a resource. This is a fairly typical thing to do in C++, whether it be a file descriptor, a lock, a thread, a GUI object handle, or a chunk of memory. The resource has its own lifetime and is allocated, used and deallocated.

To begin then we would always deal with allocation and deallocation. That means constructor(s) and destructor:

    class T {
      public:
        T( size_t s ) :
            mResource( s ? new uint8_t[s] : nullptr )
            mSize(s) {}
        ~T() {
            delete[] mResource;
        }

      private:
        uint8_t *mResource;
        int mSize;
    };

Once we start using this resource though, we want to be able to copy it.

    T obj1(100);
    T obj2(obj1);

Let’s write a reasonable one:

    class T {
        // ...
        // Use C++11 facility for calling constructors
        T( const T &O ) :
            T(O.s)
        {
            // Use STL algorithm, copy(), to do the hard work
            copy(O.mResource, O.mResource + O.mSize, mResource);
        }
        // ...
    };

Once we have a copy constructor we’ll also want an assignment operator. Before I learned the magic trick under discussion, here’s the sort of code I might have written:

    class T {
        // ...
        T& operator=T( const T &O )
        {
            if( &O == this )
                return *this;
            delete[] mResource;
            mResource = new uint8_t[O.mSize];
            mSize = O.mSize;
            return *this;
        }
        // ...
    };

Hopefully you can see how unsatisfactory that is. We’ve had to duplicate the destructor and constructor code inside the assignment operator. As the object becomes more complicated we’ll have to maintain that connection – every change to constructor or destructor has to be repeated. Yuck. I had thought that was just a C++ annoyance. Not so. Here’s the copy-and-swap idiom:

    class T {
        // ...
        T& operator=T( T O )
        {
            swap(O);
            return *this;
        }

        void swap( T &O )
        {
            // STL swap from the utility/algorithm include
            using std::swap;
            swap(mResource, O.mResource);
            swap(mSize, O.mSize);
        }
        // ...
    };

The most important change is that the parameter to operator=() is no longer a const reference, it’s a value. This would normally be have your C++ spidey-sense worrying about unnecessary copies. Remember though, in this case we are performing a necessary and requested copy.

    T a(100);
    T b(0);

    b = a;

What happens in the b = a line now? The compiler sees that there is an operator=() available, but it uses pass-by-value. It therefore effectively does this:

    T a(100);
    T b(0);

    b.operator=(T(a));

In other words, it calls the copy constructor with a to make a new temporary T. This does a deep-copy before operator=() even begins. Then operator=() does one thing: swap() to switch the resources owned by the temporary and the destination. That leaves us with a temporary, about to be deleted, pointing at b’s old resources and b pointing at the copy-constructed from a resources. Then operator=() ends and the temporary goes out of scope, causing its destructor to free up b’s original resources.

I think that’s magical – remember that the code duplication was of the destructor code to free up the original resources (now handled when the temporary goes out of scope using the destructor itself); and of the copy-constructor code to perform the deep-copy (now done automatically by the creation of the temporary using the copy-constructor itself). There is no need to check for self-assignment, since the temporary cannot be an alias for anything – it’s newly created. What’s more there is not a pointer in sight.

Things get even better when we introduce C++11’s new move-semantics. Move semantics in C++11 are used to bypass unnecessary copy operations when the thing being copied from is a temporary. Consider this:

    T b(T(50));

b is being assigned to as a copy of a temporary object. Before C++11, the compiler would call the constructor to make a temporary, then call b’s copy constructor to deep-copy that temporary to b, then destruct the temporary. That’s pretty wasteful; we had to pay for a copy we didn’t want, and an allocation and destruction we didn’t want. Plus we needed twice the memory we eventually used. (This isn’t actually what happens, the compiler is allowed to use something called copy elision in this case to avoid the copy, but there are other more complex cases where copy elision isn’t possible).

C++11 lets us catch this case separately from normal copy construction. When the thing being copied from a temporary (C++11 calls them rvalues), a different constructor will be looked for in preference to the copy-constructor – the move-constructor:

    class T {
        // ...
        T( T &&O ) :
            T(0)
        {
            swap(O);
        }
        // ...
    };

The identifying feature is the double-ampersand and lack of const on the argument, which means an rvalue reference, and will match a temporary. The particular feature of the move constructor is that we are allowed to do whatever we like to the source object because we know it’s not going to exist once the current C++ statement is complete (i.e. until the next semi-colon).

We first construct the smallest possible valid object T(0), and then use the same copy-and-swap trick we did earlier to swap the resources of the rvalue into the newly constructed object. That leaves the temporary resource pointing at the “not-used” state. When it destructs, it takes no resources with it, but we are left with the resources it did have.

    T b(T(50));

Now this line goes much faster. A temporary, T(50) is constructed, b’s move constructor is called with that temporary, which constructs a T(0) and then swaps it for the temporary T(50). That leaves b set up as a T(50) and the temporary as a T(0), which is then destructed at negligible cost. b is exactly what we want, and it cost us one full construction and one negligible construction; no deep-copy was needed and no extra memory was used.

Key elements that make move-construction practical:

  • Members that are swap()able. For fast swaps, the members should themselves be move-constructable and move-assignable.
  • Valid-but-empty state. That is to say that you can quickly construct an resource-free version of the object (our T(0) above allocated nothing, leaving the resource pointer set to nullptr).
  • A fast destructor. In its empty-but-valid state, the destructor shouldn’t have to do anything. That’s the case with our sample T, because the nullptr resource costs nothing to delete.

Not having the above doesn’t make move-construction impossible, but it would deny you some of the speed advantages.

This entry was posted in FussyLogic and tagged , , . Bookmark the permalink. Trackbacks are closed, but you can post a comment.

Post a Comment

You must be logged in to post a comment.