Is the copy and swap idiom still useful in C++11

aCuria picture aCuria · Oct 16, 2012 · Viewed 10.5k times · Source

I refer to this question: What is the copy-and-swap idiom?

Effectively, the above answer leads to the following implementation:

class MyClass
{
public:
    friend void swap(MyClass & lhs, MyClass & rhs) noexcept;

    MyClass() { /* to implement */ };
    virtual ~MyClass() { /* to implement */ };
    MyClass(const MyClass & rhs) { /* to implement */ }
    MyClass(MyClass && rhs) : MyClass() { swap(*this, rhs); }
    MyClass & operator=(MyClass rhs) { swap(*this, rhs); return *this; }
};

void swap( MyClass & lhs, MyClass & rhs )
{
    using std::swap;
    /* to implement */
    //swap(rhs.x, lhs.x);
}

However, notice that we could eschew the swap() altogether, doing the following:

class MyClass
{
public:
    MyClass() { /* to implement */ };
    virtual ~MyClass() { /* to implement */ };
    MyClass(const MyClass & rhs) { /* to implement */ }
    MyClass(MyClass && rhs) : MyClass() { *this = std::forward<MyClass>(rhs);   }
    MyClass & operator=(MyClass rhs)
    { 
        /* put swap code here */ 
        using std::swap;
        /* to implement */
        //swap(rhs.x, lhs.x);
        // :::
        return *this;
    }
};

Note that this means that we will no longer have a valid argument dependent lookup on std::swap with MyClass.

In short is there any advantage of having the swap() method.


edit:

I realized there is a terrible mistake in the second implementation above, and its quite a big thing so I will leave it as-is to instruct anybody who comes across this.

if operator = is defined as

MyClass2 & operator=(MyClass2 rhs)

Then whenever rhs is a r-value, the move constructor will be called. However, this means that when using:

MyClass2(MyClass2 && rhs)
{
    //*this = std::move(rhs);
}

Notice you end up with a recursive call to the move constructor, as operator= calls the move constructor...

This is very subtle and hard to spot until you get a runtime stack overflow.

Now the fix to that would be to have both

MyClass2 & operator=(const MyClass2 &rhs)
MyClass2 & operator=(MyClass2 && rhs)

this allows us to define the copy constructors as

MyClass2(const MyClass2 & rhs)
{
    operator=( rhs );
}

MyClass2(MyClass2 && rhs)
{
    operator=( std::move(rhs) );
}

Notice that you write the same amount of code, the copy constructors come "for-free" and you just write operator=(&) instead of the copy constructor and operator=(&&) instead of the swap() method.

Answer

Christian Rau picture Christian Rau · Oct 17, 2012

First of all, you're doing it wrong anyway. The copy-and-swap idiom is there to reuse the constructor for the assignment operator (and not the other way around), profiting from already properly constructing constructor code and guaranteeing strong exception safety for the assignment operator. But you don't call swap in the move constructor. In the same way the copy constructor copies all data (whatever that means in the given context of an individual class), the move constructor moves this data, your move constructor constructs and assigns/swaps:

MyClass(const MyClass & rhs) : x(rhs.x) {}
MyClass(MyClass && rhs) : x(std::move(rhs.x)) {}
MyClass & operator=(MyClass rhs) { swap(*this, rhs); return *this; }

And this would in your alternative version just be

MyClass(const MyClass & rhs) : x(rhs.x) {}
MyClass(MyClass && rhs) : x(std::move(rhs.x)) {}
MyClass & operator=(MyClass rhs) { using std::swap; swap(x, rhs.x); return *this; }

Which doesn't exhibit the severe error introduced by calling the assignment operator inside the constructor. You should never ever call the assignment operator or swap the whole object inside a constructor. Constructors are there to care for construction and have the advantage of not having to care for the, well, destruction of the previous data, since that data doesn't exist yet. And likewise can constructors handle types not default constructible and last but not least often direct construction can be more performant than defualt construction followed by assignment/swap.

But to answer your question, this whole thing is still the copy-and-swap idiom, just without an explicit swap function. And in C++11 it is even more useful because now you have implemented both copy and move assignment with a single function.

If the swap function is still of value outside of the assignment operator is an entirely different question and depends if this type is likely to be swapped, anyway. In fact in C++11 types with proper move semantics can just be swapped sufficiently efficient using the default std::swap implementation, often eliminating the need for an additional custom swap. Just be sure not to call this default std::swap inside of your assignment operator, since it does a move assignment itself (which would lead to the same problems as your wrong implementation of the move constructor).

But to say it again, custom swap function or not, this doesn't change anything in the usefulness of the copy-and-swap idiom, which is even more useful in C++11, eliminating the need to implement an additional function.