assignment operator swap idiom

I’ve been writing C++ since about 1986.  This post is more of a reminder than a tutorial.  One of the things I get junior C++ programmers to show me at interview, is writing a C++ assignment operator.

Problem

The C++ assignment operator is subtle, and very easy to get wrong.  If you are writing a “smart pointer” (which is neither) to manage reference counted objects, when the code explodes and blows your foot off the cause is actually several mountains away.

Note that this blog post is a restatement of the work of other brilliant people.

class example
{
private:
    int member;
public:
    ~example() { release(member)}
    example(const example &rhs) : member(copy(rhs.member)) { }
    example &operator=(const example &rhs)   
    {
         if (this != &rhs)
         {
             release(member);
             member = copy(rhs.member);
         }
         return *this;
    }
};

OK very small example, not actually a “smart pointer” but sufficient to demonstrate the solution.  This little example has some things to take note of:

  • The copy construtor’s code is duplicated in the assignment operator (bitrot: they will inevitably diverge over time).
  • The destructor’s code is duplicated in the assignment operator (bitrot: they will inevitabley diverge over time).

example::swap

The next step requires adding another method to the class, but one that is used extensively by the C++ standard library.

class example
{
private:
    // just the swap method for brevity
    void swap(example &rhs)
    {
        int temp = member;
        member = rhs.member;
        rhs.member = temp;
    }
};

Note that the swap is a shallow copy. If it were managing a reference counted object, there is no need to adjust the reference counts.

Code-Reuse

It is possible to the the swap method to have the compiler re-use the destructor code and re-use the copy constructor code.

class example
{
public:
    // just the assignment operator for brevity
    example &operator=(const example &rhs)    
    {
        example temp(rhs);
        temp.swap(*this);
        return *this;
    }
};

Things to note:

  • the copy constructor code is re-used,
  • the destructor code is re-used when destroying temp.
  • It works elegantly when faced with self assignment.

Just one more thing to note, if you are following the instance lifetimes carefully, it is possible to write this one line shorter, if the temporary is anonymous.

class example
{
    // just the assignment operator for brevity
    example &operator=(const example &rhs)    
    {
        example(rhs).swap(*this);
        return *this;
    }
};

But at the cost of possibly confusing later readers.

The Moral of Our Story

  1. Only destroy instance things in the destructor.  If you are destroying instance things in any other method, you are looking at a bug.
  2. If you find yourself writing your own “smart pointer” stop.  Use the ones from the Standard C++ library.  They are exception-proof and thread-proof.