views:

1410

answers:

2

What is this idiom and when should it be used? Which problems does it solve? Will the idiom change when C++0x is used?

Although it's been mentioned in many places, we didn't have any singular "what is it" question and answer, so here it is. Here is a partial list of places where it was previously mentioned:

+85  A: 

A class that needs to manually manage any resources also needs to implement The Big Three. The copy-and-swap idiom applies to such classes, and elegantly assists the assignment operator in achieving two things: removing code duplication, and providing a strong exception guarantee.

Conceptually, it works by using the copy-constructor to create a copy of the data, then a swap function to swap the old data with this new data. The temporary copy is then destructed, taking the old data with it, and we are left with a copy of the new data.


Let's consider an explained concrete case:

class dumb_array
{
public:
    dumb_array(std::size_t pSize = 0) :
    mSize(pSize),
    mArray(mSize ? new int[mSize]() : 0)
    {}

    dumb_array(const dumb_array& pOther) :
    mSize(pOther.mSize),
    mArray(mSize ? new int[mSize]() : 0),
    {
        std::copy(pOther.mArray, pOther.mArray + mSize, mArray);
    }

    ~dumb_array(void)
    {
        delete [] mArray;
    }

private:
    std::size_t mSize;
    int* mArray;
};

This class almost manages an array successfully, but it needs operator= to operate correctly and we'll be done. Here's how a naive implementation might look:

// the hard part
dumb_array& operator=(const dumb_array& pOther)
{
    if (this != &pOther) // (1)
    {
        // get rid of the old data
        delete [] mArray; // (2)
        mArray = 0;

        // and put in the new
        mSize = pOther.mSize;
        mArray = mSize ? new int[mSize]() : 0; // (3)
        std::copy(pOther.mArray, pOther.mArray + mSize, mArray);             
    }

    return *this;
} 

This now manages an array, without leaks; however, it suffers from three problems.

The first is the self-assignment test. While this check is an easy way to prevent us from running needless code (and therefore also provides a no-throw guarantee) on self-assignment and from introducing subtle bugs (such as deleting the array only to try and copy it), in all other cases it merely serves to slow the program down. Self-assignment rarely occurs, so most of the time this check is a waste. It would be better if the operator could work properly without it.

The second is that it only provides a basic exception guarantee. If new int[] fails, *this will have been modified (namely, the size is wrong and the data is gone). For a strong exception guarantee, it would need to be something akin to:

dumb_array& operator=(const dumb_array& pOther)
{
    if (this != &pOther) // (1)
    {
        // we need to get the new data ready before we replace the old
        std::size_t newSize = pOther.mSize;
        int* newArray = newSize ? new int[newSize]() : 0; // (3)
        std::copy(pOther.mArray, pOther.mArray + newSize, newArray);

        // replace the old data
        delete [] mArray;
        mSize = newSize;
        mArray = newArray;
    }

    return *this;
} 

The code has expanded...and this is only for one resource! With multiple resources to copy, we need to introduce a try/catch block and keep track of what to free if an exception is thrown. Far too messy. (Though having to manage multiple resources in a single class is a bad thing!)

The third problem is code duplication. In our case, it's only a single line with std::copy, but with more complex resources and/or multiple resources this code bloat can be quite a hassle. And we should strive to never repeat ourselves.


As mentioned, the copy-and-swap idiom will fix all these issues. It requires a working copy-constructor, which we have (as required by implementing The Big Three), and a swap function, which we don't necessarily have. While The Big Three dictate we implement everything we have, it should really be called "The Big Three and A Half": almost any time you manually manage a resource it also makes sense to provide a swap function, for optimal swaps.

That is, std::swap will normally copy an entire object, and then perform two assignments, then discard of the copy. This works fine for primitive types, but for expensive classes this won't do. Rather, we should just swap all the internal members of the class and then the classes are effectively swapped.

We do that as follows:

class dumb_array
{
public:
    // ...

    void swap(dumb_array& pOther) // nothrow
    {
        using std::swap; // allow ADL

        swap(mSize, pOther.mSize); // with the internal members swapped,
        swap(mArray, pOther.mArray); // *this and pOther are effectively swapped
    }
};

// the following isn't strictly necessary for the copy-and-swap
// idiom, but if we're going to implement swap let's do it right.
namespace std
{
    // adding things to the std namespace leads to undefined
    // behavior, unless it's a specialization.
    template <> // <-- so this is important!
    void swap(dumb_array& pFirst, dumb_array& pSecond)
    {
        pFirst.swap(pSecond);
    }
}

Now using std::swap on dumb_array's is much more efficient; it just swaps pointers and sizes, rather than copying and assigning entire arrays. Aside from this bonus in functionality and efficiency, we are now ready to implement the copy-and-swap idiom.

The assignment operator is thus:

dumb_array& operator=(dumb_array pOther) // (1)
{
    swap(pOther); // (2)
    return *this;
} 

And that's it! With one fell swoop, all three problems are elegantly tackled at once. Let's dissect how it works.

We first notice a design choice: the parameter is taken by-value. While one could just as easily do the following (and indeed, many naive implementations of the idiom do):

dumb_array& operator=(const dumb_array& pOther)
{
    dumb_array(pOther).swap(*this);
    return *this;
}

We lose an important optimization opportunity. The article details why, but the guideline is: if you're just going to make a copy anyway, let the compiler do it in the parameter list. (This gives it the opportunity to skip a copy when working with rvalues.) (Note before you accept this is a rule to follow for all eternity, it does not apply in C++0x! More on this below.)

Either way, this method of copying is the key to eliminating code duplication: we get to use the code from the copy-constructor to make the copy, and never need to repeat any bit of it. Now that the copy is made, we are ready to swap.

Observe that upon entering the function all the new data is already allocated, copied, and ready to be used. This is what gives us a strong exception guarantee for free: we won't even get to the swap if construction of the copy fails, and it's therefore not possible to alter the state of *this. (What we manually did before for a strong exception guarantee, the compiler is doing for us; how kind.)

At this point we are home-free, because swap is non-throwing. We swap our current data with the copied data, safely altering our state, and the old data gets put into the temporary. The old data is then released when the function returns. (Where upon the parameter's scope ends and its destructor is called.)

Because the idiom cleanly separates the constructive part from the destructive part, we cannot introduce bugs within the operator. This means we get rid of the need for a self-assignment check, allowing a single uniform implementation of operator=. (Additionally, we no longer have a performance penalty on non-self-assignments.)

And that is the copy-and-swap idiom.


In C++0x, we won't need to implement std::swap manually anymore. This is because instead of copying things around, std::swap will move them around; with proper move-semantics this ultimately results in the same code as our custom swap.

I have expressed the ideal C++0x resource managing class on my blog, which discusses the effects move-semantics have on The Big Three (it's The Big Four now), swapping and swap, and the C++0x version of both copy-and swap and move-and-swap.

While you can see the rationale on the blog, for the impatient here's the result:

class dumb_array
{
public:
    // constructor
    dumb_array(std::size_t pSize = 0) :
    mSize(pSize),
    mArray(mSize ? new int[mSize]() : nullptr)
    {}

    // copy constructor
    dumb_array(const dumb_array& pOther) :
    mSize(pOther.mSize),
    mArray(mSize ? new int[mSize]() : nullptr),
    {
        std::copy(pOther.mArray, pOther.mArray + mSize, mArray);
    }

    // move constructor
    dumb_array(dumb_array&& pOther) :
    dumb_array() // delegate
    {
        swap(pOther);
    }

    // assignment operator
    dumb_array& operator=(dumb_array pOther)
    {
        swap(pOther);

        return *this;
    }

    // destructor
    ~dumb_array(void)
    {
        delete [] mArray;
    }

    // swap
    void swap(dumb_array& pOther)
    {    
        std::swap(mSize, pOther.mSize);
        std::swap(mArray, pOther.mArray);
    }

private:
    std::size_t mSize;
    int* mArray;
};

Enjoy.

GMan
+1 for filling the gap with such a clear and thorough explanation.
Péter Török
Your first piece of code doesn't provide the basic guarantee. If `new int[]` fails, `mArray` will point to garbage. The object isn't destructible.
sbi
Also note that copy-and-swap also needs a dtor. (Actually that goes without saying, but since you mention the cctor, you might as well mention the dtor.)
sbi
+1 I would replace `pRhs.swap(*this)` with `swap(pRhs)`, but that's just a matter of personal opinion and/or style.
FredOverflow
@sbi: Oops, good eye. I've also added a note about C++0x with concerns to swap and this idiom. @Fred: Eh, I'd say it's better. I was actually having a coder's block when I wrote that. I was thinking "I know I usually write this much more concise, what is it..." Derp. :) Also, sbi means my naive `operator=`.
GMan
@GMan: A custom overload of swap is still potentially faster, because move semantics has to set source pointers to zero after copying them.
FredOverflow
@Fred: An optimizing compiler can easily see that such an assignment is wasteful, though. I demonstrate that in my answer I linked to in the post. You may have a point, though, that without such things a `swap` might still be faster. I wouldn't consider it worth it anymore, though.
GMan
@GMan: I agree, that's what I generally mean when I say "potentially faster" -- probably does not matter in practice :)
FredOverflow
@Fred: Ah, you sneaked the safe word in. :P
GMan
@GMan: If we rely on move semantics, the class definitely needs a move cctor and a move assignment operator. So this actually has more requirements than doing your own `swap()`. (I'm not arguing against doing so, just that this should be mentioned.)
sbi
@GMan: Ah, I now saw that you already mention this in a code comment.
sbi
@sbi: It is, "As long as one implements move-semantics.". :) Unless I'm misunderstanding. (EDIT: Guess not. :P) I'll make the C++0x section more concrete, though. By the way, I think the dtor requirement is made explicit when I say "this is for classes that implement The Big Three." I'll see if I can sneak it it.
GMan
@GMan: I would argue that a class managing several resources at once is doomed to fail (exception safety becomes nightmarish) and I would strongly recommend that either a class manages ONE resource OR it has business functionality and use managers.
Matthieu M.
@GMan nice one :) I wonder why you do that test against a zero size, though? [Dynamic Zero size arrays are OK](http://herbsutter.com/2009/09/02/when-is-a-zero-length-array-okay/).
Johannes Schaub - litb
Johannes Schaub - litb
@Johannes: I know zero-size dynamic arrays are okay, but I did the check to 1) avoid having to get any dynamic memory and 2) make it look more complex. :P And good to know it is sufficient, I'll admit I was a bit confused what he was talking about.
GMan
@GMan ahh i see now :)
Johannes Schaub - litb
@Matthieu: I agree, I've added that point. @Johannes: I've touched it up a bit in regards to self-assignment.
GMan
omg that is an amazing explanation!
Jamie Cook
Very nice summary! I'd personally comment out `throw()`. Leave the text there to indicate you don't think the function will throw, but leave off the potential penalties: http://www.boost.org/development/requirements.html#Exception-specification
Bill
@Bill: You're right, I would (and do) too.
GMan
@GMan: or you use the C++0x `nothrow` qualifier :-)
Matthieu M.
+1 Nice explanation
Chubsdad
@Chubsdad: Thanks. Good timing, I was planning to (and just did) improve and expand on C++0x changes.
GMan
Big four? (1) dtor, (2) copy ctor, (3) copy op=, (4) move ctor, and (5) move op=. Which of those doesn't get counted?
James McNellis
@James: There's only one assignment operator.
GMan
@GMan: You can declare both a copy op= and a move op=, can't you? (The implicit move op= is suppressed if a copy op= is implemented, though). Or, that's my understanding. Why am I wrong?
James McNellis
@James: No, you're right, you can. You just take it by value in both C++03 and C++0x. (In C++03, lvalues get copied, rvalues hopefully get their copy elided, in C++0x lvalues get copied, ravlues get moved, and sometimes hopefully elided).
GMan
Oh. I see what you mean now.
James McNellis
+15  A: 

Assignment, at its heart, is two steps: tearing down the object's old state and building its new state.

Basically, that's what the destructor and the copy constructor do, so the first idea would be to delegate the work to them. However, since destruction mustn't fail, while construction might, we actually want to do it the other way around: first perform the constructive part and if that succeeded, then do the destructive part. The copy-and-swap idiom is a way to do just that: It first calls a class' copy constructor to create a temporary, then swaps its data with the temporary's, and then lets the temporary's destructor destroy the old state.
Since swap() is supposed to never fail, the only part which might fail is the copy-construction. That is performed first, and if it fails, nothing will be changed in the targeted object.

In its refined form, copy-and-swap is implemented by having the copy performed by initializing the (non-reference) parameter of the assignment operator:

T& operator=(T tmp)
{
    this->swap(tmp);
    return *this;
}
sbi
Terse, just the idea!
legends2k