20

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.

Community
  • 1
  • 1
aCuria
  • 6,935
  • 14
  • 53
  • 89
  • 5
    That's still copy-and-swap, without a specific `swap` function for `MyClass`. What did `*this = std::forward(rhs); /* that should be 'move', btw */` buy you over `swap(*this, rhs);`? You've only delayed the call. – Xeo Oct 16 '12 at 19:39
  • 2
    Your use of forward here is incorrect in that it's needlessly being used (it should be used within template functions, where the `T` in `std::forward` could actually be changed; here it's always just `MyClass`). Just use `std::move`. And yeah, this seems to be a bit more concise, I personally like keeping assignment and swapping separate (the latter just happens to use the former). – GManNickG Oct 16 '12 at 19:43
  • I will take note of the proper use of forward. I was thinking along the lines of if some stl functions would actually call swap() on your code, then perhaps having the swap function is better – aCuria Oct 16 '12 at 19:49
  • @Xeo The second version has less complicated, and is easier for anyone not familiar with the swap idiom/mantra/incantation to understand. All things equal, I would prefer the more concise method. – aCuria Oct 16 '12 at 19:53

3 Answers3

24

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.

Christian Rau
  • 45,360
  • 10
  • 108
  • 185
  • +1 But this begs the question: why not use swap() in the move constructor? all swap() does in this case is swap(x, rhs.x) which is the same as what you do in the constructor? – aCuria Oct 17 '12 at 11:34
  • @aCuria Because a move constructor, well, move-constructs. It is conceptually wrong to default construct and then swap/assign. Why swap with something that **doesn't actually exist yet**? In the best case it is usually also performance-wise wrong (in addition to being conceptually wrong) and in the worst case it can result in errors similar to your error with the assignment if becoming practice. Well, the best case would actually be those where it won't compile at all, for not default-constructible classes... – Christian Rau Oct 17 '12 at 11:40
  • @aCuria ... Also think about the move-assignment in this case. You're swapping the source with a temporary and then swapping the temporary with the destination. Performance aside, is this proper and reasonable behaviour? – Christian Rau Oct 17 '12 at 11:41
  • 1
    *"which is the same as what you do in the constructor"* - No, it isn't. My constructor is move constructing (whatever that means for the indiviual member type). Moving is **not** the same as swapping. Since the values moved into aren't actually existent, they are currently constructed. So there just **is nothing** you could swap into `rhs.x`. In the same way `MyClass(const MyClass &rhs) { x = rhs.x; }` is conceptually wrong compared to `MyClass(const MyClass &rhs) : x(rhs.x) {}`, swapping is (in the constructor) conceptually wrong comapred to moving, no matter if resulting in similar code. – Christian Rau Oct 17 '12 at 11:50
  • @aCuria Delve a little deeper into the difference between assignment an initialization to grasp the difference between those two concepts. – Christian Rau Oct 17 '12 at 11:53
  • +1 I think I get what you mean, moving rhs.x into x is not always the same as swapping rhs.x and x, depending on what x actually is – aCuria Oct 17 '12 at 14:28
  • I think your second code snippet is the best in C++11. Since the first would need an extra swap implementation which you can avoid in the 2nd case. So you still have a third member-wise copy/move/swap thingy beside the copy and move constructors. – mmmmmmmm May 28 '13 at 10:22
  • Not sure I fully follow this. Are you claiming that with the code snippets above, you don't need to implement swap, but just use std::swap? std::swap calls move assignment, and move assignment is calling swap, so this appears to be mutually recursive if swap were not explicitly defined. Better to define move assignment independent of swap, and use std::swap. Then define copy assignment in terms of other functions. – Nir Friedman Aug 12 '15 at 16:48
2

You're certainly not considering the whole picture. Your code re-uses constructors for different assignment operators, the original re-uses assignment operators for different constructors. This is basically the same thing, all you've done is shift it around.

Except that since they write constructors, they can deal with non-default-constructible types or types whose values are bad if not initialized explicitly like int or are plain expensive to default-construct or where the default-constructed members are not valid to destruct (for example, consider a smart pointer- an uninitialized T* leads to a bad delete).

So basically, all you've achieved is the same principle but in a decidedly worse place. Oh, and you had to define all four functions, else mutual recursion, whereas the original copy-and-swap only defined three functions.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • You are incorrect about the number of functions declared. its "swap, assignment, cctor, mctor" vs "assignment(&), assignment(&&), cctor, mctor" therefore 4 functions both ways – aCuria Oct 17 '12 at 10:34
  • @aCuria This only applies to your version with the extra `swap` method. But you can just do it without an extra `swap` function and still define only one assignment operator, i.e. when not writen an incorrect constructor, as demonstrated in my answer. – Christian Rau Oct 17 '12 at 10:51
1

The validity of the reasons (if any) to use the copy-and-swap idiom to implement copy assignment are the same in C++11 as they are in previous versions.

Also note that you should use std::move on member variables in the move constructor, and you should use std::move on any rvalue references that are function parameters.

std::forward should only be used for template parameter references of the form T&& and also auto&& variables (which both may be subject to reference folding into lvalue references during type deduction) to preserve their rvalueness or lvalueness as appropriate.

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319