Good idea to have a full-fledge explanation for this idiom, it's so common that everyone should know about it.
– Matthieu M.Jul 19 '10 at 11:52

9

Warning: The copy/swap idiom is used far more frequently than it is useful. It is often harmful to performance when a strong exception safety guarantee is not needed from copy assignment. And when strong exception safety is needed for copy assignment, it is easily provided by a short generic function, in addition to a much faster copy assignment operator. See slideshare.net/ripplelabs/howard-hinnant-accu2014 slides 43 - 53. Summary: copy/swap is a useful tool in the toolbox. But it has been over-marketed and subsequently has often been abused.
– Howard HinnantMar 16 '16 at 3:08

2

@HowardHinnant: Yeah, +1 to that. I wrote this at a time where nearly every C++ question was "help my class crashes when a copy it" and this was my response. It's appropriate when you just want working copy-/move-semantics or whatever so you can move on to other things, but it's not really optimal. Feel free to put a disclaimer at the top of my answer if you think that'll help.
– GManNickGMar 16 '16 at 3:57

5 Answers
5

Overview

Why do we need the copy-and-swap idiom?

Any class that manages a resource (a wrapper, like a smart pointer) needs to implement The Big Three. While the goals and implementation of the copy-constructor and destructor are straightforward, the copy-assignment operator is arguably the most nuanced and difficult. How should it be done? What pitfalls need to be avoided?

How does it work?

Conceptually, it works by using the copy-constructor's functionality to create a local copy of the data, then takes the copied data with a swap function, swapping the old data with the new data. The temporary copy then destructs, taking the old data with it. We are left with a copy of the new data.

In order to use the copy-and-swap idiom, we need three things: a working copy-constructor, a working destructor (both are the basis of any wrapper, so should be complete anyway), and a swap function.

A swap function is a non-throwing function that swaps two objects of a class, member for member. We might be tempted to use std::swap instead of providing our own, but this would be impossible; std::swap uses the copy-constructor and copy-assignment operator within its implementation, and we'd ultimately be trying to define the assignment operator in terms of itself!

(Not only that, but unqualified calls to swap will use our custom swap operator, skipping over the unnecessary construction and destruction of our class that std::swap would entail.)

An in-depth explanation

The goal

Let's consider a concrete case. We want to manage, in an otherwise useless class, a dynamic array. We start with a working constructor, copy-constructor, and destructor:

And we say we're finished; this now manages an array, without leaks. However, it suffers from three problems, marked sequentially in the code as (n).

The first is the self-assignment test. This check serves two purposes: it's an easy way to prevent us from running needless code on self-assignment, and it protects us from subtle bugs (such as deleting the array only to try and copy it). But in all other cases it merely serves to slow the program down, and act as noise in the code; 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[mSize] 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:

The code has expanded! Which leads us to the third problem: code duplication. Our assignment operator effectively duplicates all the code we've already written elsewhere, and that's a terrible thing.

In our case, the core of it is only two lines (the allocation and the copy), but with more complex resources this code bloat can be quite a hassle. We should strive to never repeat ourselves.

(One might wonder: if this much code is needed to manage one resource correctly, what if my class manages more than one? While this may seem to be a valid concern, and indeed it requires non-trivial try/catch clauses, this is a non-issue. That's because a class should manage one resource only!)

A successful solution

As mentioned, the copy-and-swap idiom will fix all these issues. But right now, we have all the requirements except one: a swap function. While The Rule of Three successfully entails the existence of our copy-constructor, assignment operator, and destructor, it should really be called "The Big Three and A Half": any time your class manages a resource it also makes sense to provide a swap function.

We need to add swap functionality to our class, and we do that as follows†:

(Here is the explanation why public friend swap.) Now not only can we swap our dumb_array's, but swaps in general can be more efficient; it merely swaps pointers and sizes, rather than allocating and copying entire arrays. Aside from this bonus in functionality and efficiency, we are now ready to implement the copy-and-swap idiom.

We lose an important optimization opportunity. Not only that, but this choice is critical in C++11, which is discussed later. (On a general note, a remarkably useful guideline is as follows: if you're going to make a copy of something in a function, let the compiler do it in the parameter list.‡)

Either way, this method of obtaining our resource 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 that 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 enter the function if construction of the copy fails, and it's therefore not possible to alter the state of *this. (What we did manually before for a strong exception guarantee, the compiler is doing for us now; 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 repeats no code, we cannot introduce bugs within the operator. Note that this means we are 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.

What about C++11?

The next version of C++, C++11, makes one very important change to how we manage resources: the Rule of Three is now The Rule of Four (and a half). Why? Because not only do we need to be able to copy-construct our resource, we need to move-construct it as well.

What's going on here? Recall the goal of move-construction: to take the resources from another instance of the class, leaving it in a state guaranteed to be assignable and destructible.

So what we've done is simple: initialize via the default constructor (a C++11 feature), then swap with other; we know a default constructed instance of our class can safely be assigned and destructed, so we know other will be able to do the same, after swapping.

(Note that some compilers do not support constructor delegation; in this case, we have to manually default construct the class. This is an unfortunate but luckily trivial task.)

Why does that work?

That is the only change we need to make to our class, so why does it work? Remember the ever-important decision we made to make the parameter a value and not a reference:

dumb_array& operator=(dumb_array other); // (1)

Now, if other is being initialized with an rvalue, it will be move-constructed. Perfect. In the same way C++03 let us re-use our copy-constructor functionality by taking the argument by-value, C++11 will automatically pick the move-constructor when appropriate as well. (And, of course, as mentioned in previously linked article, the copying/moving of the value may simply be elided altogether.)

And so concludes the copy-and-swap idiom.

Footnotes

*Why do we set mArray to null? Because if any further code in the operator throws, the destructor of dumb_array might be called; and if that happens without setting it to null, we attempt to delete memory that's already been deleted! We avoid this by setting it to null, as deleting null is a no-operation.

†There are other claims that we should specialize std::swap for our type, provide an in-class swap along-side a free-function swap, etc. But this is all unnecessary: any proper use of swap will be through an unqualified call, and our function will be found through ADL. One function will do.

‡The reason is simple: once you have the resource to yourself, you may swap and/or move it (C++11) anywhere it needs to be. And by making the copy in the parameter list, you maximize optimization.

@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.Jul 19 '10 at 11:53

@asd: To allow it to be found through ADL.
– GManNickGDec 13 '11 at 6:11

7

@neuviemeporte: With the parenthesis, the arrays elements are default initialized. Without, they are uninitialized. Since in the copy constructor we'll be overwriting the values anyway, we can skip initialization.
– GManNickGJul 19 '12 at 15:01

8

@neuviemeporte: You need your swap to be found during ADL if you want it to work in most generic code that you'll come across, like boost::swap and other various swap instances. Swap is a tricky issue in C++, and generally we've all come to agree that a single point of access is best (for consistency), and the only way to do that in general is a free function (int cannot have a swap member, for example). See my question for some background.
– GManNickGJul 19 '12 at 22:25

Assignment, at its heart, is two steps: tearing down the object's old state and building its new state as a copy of some other object's 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:

I think that mentioning the pimpl is as important as mentioning the copy, the swap and the destruction. The swap isn't magically exception-safe. It's exception-safe because swapping pointers is exception-safe. You don't have to use a pimpl, but if you don't then you must make sure that each swap of a member is exception-safe. That can be a nightmare when these members can change and it is trivial when they're hidden behind a pimpl. And then, then comes the cost of the pimpl. Which leads us to the conclusion that often exception-safety bears a cost in performance.
– wilhelmtellDec 22 '10 at 14:41

@wilhelmtell: In C++03, there is no mention of exceptions potentially thrown by std::string::swap (which is called by std::swap). In C++0x, std::string::swap is noexcept and must not throw exceptions.
– James McNellisDec 22 '10 at 15:24

1

@sbi @JamesMcNellis ok, but the point still stands: if you have members of class-type you must make sure swapping them is a no-throw. If you have a single member that is a pointer then that's trivial. Otherwise it isn't.
– wilhelmtellDec 22 '10 at 15:34

2

@wilhelmtell: I thought that was the point of swapping: it never throws and it is always O(1) (yeah, I know, std::array...)
– sbiDec 22 '10 at 15:50

There are some good answers already. I'll focus mainly on what I think they lack - an explanation of the "cons" with the copy-and-swap idiom....

What is the copy-and-swap idiom?

A way of implementing the assignment operator in terms of a swap function:

X& operator=(X rhs)
{
swap(rhs);
return *this;
}

The fundamental idea is that:

the most error-prone part of assigning to an object is ensuring any resources the new state needs are acquired (e.g. memory, descriptors)

that acquisition can be attempted before modifying the current state of the object (i.e. *this) if a copy of the new value is made, which is why rhs is accepted by value (i.e. copied) rather than by reference

swapping the state of the local copy rhs and *this is usually relatively easy to do without potential failure/exceptions, given the local copy doesn't need any particular state afterwards (just needs state fit for the destructor to run, much as for an object being moved from in >= C++11)

When should it be used? (Which problems does it solve [/create]?)

When you want the assigned-to objected unaffected by an assignment that throws an exception, assuming you have or can write a swap with strong exception guarantee, and ideally one that can't fail/throw..†

When you want a clean, easy to understand, robust way to define the assignment operator in terms of (simpler) copy constructor, swap and destructor functions.

When any performance penalty or momentarily higher resource usage created by having an extra temporary object during the assignment is not important to your application. ⁂

† swap throwing: it's generally possible to reliably swap data members that the objects track by pointer, but non-pointer data members that don't have a throw-free swap, or for which swapping has to be implemented as X tmp = lhs; lhs = rhs; rhs = tmp; and copy-construction or assignment may throw, still have the potential to fail leaving some data members swapped and others not. This potential applies even to C++03 std::string's as James comments on another answer:

@wilhelmtell: In C++03, there is no mention of exceptions potentially thrown by std::string::swap (which is called by std::swap). In C++0x, std::string::swap is noexcept and must not throw exceptions. – James McNellis Dec 22 '10 at 15:24

‡ assignment operator implementation that seems sane when assigning from a distinct object can easily fail for self-assignment. While it might seem unimaginable that client code would even attempt self-assignment, it can happen relatively easily during algo operations on containers, with x = f(x); code where f is (perhaps only for some #ifdef branches) a macro ala #define f(x) x or a function returning a reference to x, or even (likely inefficient but concise) code like x = c1 ? x * 2 : c2 ? x / 2 : x;). For example:

On self-assignment, the above code delete's x.p_;, points p_ at a newly allocated heap region, then attempts to read the uninitialised data therein (Undefined Behaviour), if that doesn't do anything too weird, copy attempts a self-assignment to every just-destructed 'T'!

⁂ The copy-and-swap idiom can introduce inefficiencies or limitations due to the use of an extra temporary (when the operator's parameter is copy-constructed):

Here, a hand-written Client::operator= might check if *this is already connected to the same server as rhs (perhaps sending a "reset" code if useful), whereas the copy-and-swap approach would invoke the copy-constructor which would likely be written to open a distinct socket connection then close the original one. Not only could that mean a remote network interaction instead of a simple in-process variable copy, it could run afoul of client or server limits on socket resources or connections. (Of course this class has a pretty horrid interface, but that's another matter ;-P).

That said, a socket connection was just an example - the same principle applies to any potentially expensive initialisation, such as hardware probing/initialisation/calibration, generating a pool of threads or random numbers, certain cryptography tasks, caches, file system scans, database connections etc..
– Tony DelroyOct 21 '14 at 5:09

There is one more (massive) con. As of current specs technically the object will not have an move-assignment operator! If later used as member of a class, the new class will not have move-ctor auto-generated! Source: youtu.be/mYrbivnruYw?t=43m14s
– user362515Feb 14 '15 at 12:55

1

The main problem with the copy assignment operator of Client is that assignment is not forbidden.
– sbiJul 22 '15 at 14:27

@GManNickG it wouldn't fit in a comment with all the images and code examples. And it's ok if people downvote, I'm sure there's someone out there who's getting the same bug; the information in this post might be just what they need.
– OleksiySep 4 '13 at 5:33

13

note that this is only a bug in the IDE code highlighting (IntelliSense)... It will compile just fine with no warnings/errors.
– AmroOct 11 '13 at 7:29

The purpose of both functions fs and fm is to give a the state that b had initially. However, there is a hidden question: What happens if a.get_allocator() != b.get_allocator()? The answer is: It depends. Let's write AT = std::allocator_traits<A>.

If AT::propagate_on_container_move_assignment is std::true_type, then fm reassigns the allocator of a with the value of b.get_allocator(), otherwise it does not, and a continues to use its original allocator. In that case, the data elements need to be swapped individually, since the storage of a and b is not compatible.

If AT::propagate_on_container_swap is std::true_type, then fs swaps both data and allocators in the expected fashion.

If AT::propagate_on_container_swap is std::false_type, then we need a dynamic check.

If a.get_allocator() == b.get_allocator(), then the two containers use compatible storage, and swapping proceeds in the usual fashion.

However, if a.get_allocator() != b.get_allocator(), the program has undefined behaviour (cf. [container.requirements.general/8].

The upshot is that swapping has become a non-trivial operation in C++11 as soon as your container starts supporting stateful allocators. That's a somewhat "advanced use case", but it's not entirely unlikely, since move optimizations usually only become interesting once your class manages a resource, and memory is one of the most popular resources.

Thank you for your interest in this question.
Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).