Copy & Move Idiom? - c++

By using the Copy & Swap idiom we can easily implement copy assignment with strong exception safety:
T& operator = (T other){
using std::swap;
swap(*this, other);
return *this;
}
However this requires T to be Swappable. Which a type automatically is if std::is_move_constructible_v<T> && std::is_move_assignable_v<T> == true thanks to std::swap.
My question is, is there any downside to using a "Copy & Move" idiom instead? Like so:
T& operator = (T other){
*this = std::move(other);
return *this;
}
provided that you implement move-assignment for T because obviously you end up with infinite recursion otherwise.
This question is different from Should the Copy-and-Swap Idiom become the Copy-and-Move Idiom in C++11? in that this question is more general and uses the move assignment operator instead of actually moving the members manually. Which avoids the issues with clean-up that predicted the answer in the linked thread.

Correction to the question
The way to implement Copy & Move has to be as #Raxvan pointed out:
T& operator=(const T& other){
*this = T(other);
return *this;
}
but without the std::move as T(other) already is an rvalue and clang will emit a warning about pessimisation when using std::move here.
Summary
When a move assignment operator exists, the difference between Copy & Swap and Copy & Move is dependent on whether the user is using a swap method which has better exception safety than the move assignment. For the standard std::swap the exception safety is identical between Copy & Swap and Copy & Move. I believe that most of the time, it will be the case that swap and the move assignment will have the same exception safety (but not always).
Implementing Copy & Move has a risk where if the move assignment operator isn't present or has the wrong signature, the copy assignment operator will reduce to infinite recursion. However at least clang warns about this and by passing -Werror=infinite-recursion to the compiler this fear can be removed, which quite frankly is beyond me why that is not an error by default, but I digress.
Motivation
I have done some testing and a lot of head scratching and here is what I have found out:
If you have a move assignment operator, the "proper" way of doing Copy & Swap won't work due to the call to operator=(T) being ambiguous with operator=(T&&). As #Raxvan pointed out, you need to do the copy construction inside of the body of the copy assignment operator. This is considered inferior as it prevents the compiler from performing copy elision when the operator is called with an rvalue. However the cases where copy elision would have applied are handled by the move assignment now so that point is moot.
We have to compare:
T& operator=(const T& other){
using std::swap;
swap(*this, T(other));
return *this;
}
to:
T& operator=(const T& other){
*this = T(other);
return *this;
}
If the user isn't using a custom swap, then the templated std::swap(a,b) is used. Which essentially does this:
template<typename T>
void swap(T& a, T& b){
T c(std::move(a));
a = std::move(b);
b = std::move(c);
}
Which means that the exception safety of Copy & Swap is the same exception safety as the weaker of move construction and move assignment. If the user is using a custom swap, then of course the exception safety is dictated by that swap function.
In the Copy & Move, the exception safety is dictated entirely by the move assignment operator.
I believe that looking at performance here is kind of moot as compiler optimizations will likely make there be no difference in most cases. But I'll remark on it anyway the copy and swap performs a copy construction, a move construction and two move assignments, compared to Copy & Move which does a copy construction and only one move assignment. Although I'm kind of expecting the compiler to crank out the same machine code in most cases, of course depending on T.
Addendum: The code I used
class T {
public:
T() = default;
T(const std::string& n) : name(n) {}
T(const T& other) = default;
#if 0
// Normal Copy & Swap.
//
// Requires this to be Swappable and copy constructible.
//
// Strong exception safety if `std::is_nothrow_swappable_v<T> == true` or user provided
// swap has strong exception safety. Note that if `std::is_nothrow_move_assignable` and
// `std::is_nothrow_move_constructible` are both true, then `std::is_nothrow_swappable`
// is also true but it does not hold that if either of the above are true that T is not
// nothrow swappable as the user may have provided a specialized swap.
//
// Doesn't work in presence of a move assignment operator as T t1 = std::move(t2) becomes
// ambiguous.
T& operator=(T other) {
using std::swap;
swap(*this, other);
return *this;
}
#endif
#if 0
// Copy & Swap in presence of copy-assignment.
//
// Requries this to be Swappable and copy constructible.
//
// Same exception safety as the normal Copy & Swap.
//
// Usually considered inferor to normal Copy & Swap as the compiler now cannot perform
// copy elision when called with an rvalue. However in the presence of a move assignment
// this is moot as any rvalue will bind to the move-assignment instead.
T& operator=(const T& other) {
using std::swap;
swap(*this, T(other));
return *this;
}
#endif
#if 1
// Copy & Move
//
// Requires move-assignment to be implemented and this to be copy constructible.
//
// Exception safety, same as move assignment operator.
//
// If move assignment is not implemented, the assignment to this in the body
// will bind to this function and an infinite recursion will follow.
T& operator=(const T& other) {
// Clang emits the following if a user or default defined move operator is not present.
// > "warning: all paths through this function will call itself [-Winfinite-recursion]"
// I recommend "-Werror=infinite-recursion" or "-Werror" compiler flags to turn this into an
// error.
// This assert will not protect against missing move-assignment operator.
static_assert(std::is_move_assignable<T>::value, "Must be move assignable!");
// Note that the following will cause clang to emit:
// warning: moving a temporary object prevents copy elision [-Wpessimizing-move]
// *this = std::move(T{other});
// The move doesn't do anything anyway so write it like this;
*this = T(other);
return *this;
}
#endif
#if 1
T& operator=(T&& other) {
// This will cause infinite loop if user defined swap is not defined or findable by ADL
// as the templated std::swap will use move assignment.
// using std::swap;
// swap(*this, other);
name = std::move(other.name);
return *this;
}
#endif
private:
std::string name;
};

My question is, is there any downside to using a "Copy & Move" idiom instead?
Yes, it you get stack overflow if you din't implement move assignmentoperator =(T&&).
If you do want to implement that you get a compiler error (example here):
struct test
{
test() = default;
test(const test &) = default;
test & operator = (test t)
{
(*this) = std::move(t);
return (*this);
}
test & operator = (test &&)
{
return (*this);
}
};
and if you do test a,b; a = b; you get the error:
error: ambiguous overload for 'operator=' (operand types are 'test' and 'std::remove_reference<test&>::type {aka test}')
One way to solve this is to use a copy constructor:
test & operator = (const test& t)
{
*this = std::move(test(t));
return *this;
}
This will work, however if you don't implement move assignment you might not get an error (depending on compiler settings). Considering human error, it's possible that this case could happen and you end up stack overflow at runtime which is bad.

Related

gcc shared_ptr copy assignment implementation

I was scanning the shared_ptr implementation in GCC 5, and I see the following:
__shared_ptr&
operator=(__shared_ptr&& __r) noexcept
{
__shared_ptr(std::move(__r)).swap(*this);
return *this;
}
My question is why the additional move construct of the temporary before the swap? I assume that the compilation will remove any additional overhead - but why not just call __r.swap(*this)? Is there some clever side effect that I'm missing?
I see that other functions in the class are also implemented using the same pattern, I can understand cases which accept a const reference, but an rvalue reference?
For a start, that's what the standard says, GCC just follows it to the letter.
That way the assignment operator has a postcondition that __r.empty() which would not be achieved by your suggestion, so implementing it as you suggest would have different effects to what the standard says, and so would be non-conforming.
i.e. this assertion holds:
auto p1 = std::make_shared<int>(1);
auto p2 = std::make_shared<int>(2);
p1 = std::move(p2);
assert( !p2 );
The "clever side effect" is that you create a new empty shared_ptr, which ends up holding the old value of *this after the swap, and then goes out of scope. That means the old value of *this doesn't end up in __r.
Because we need to call a destructor on the rvalue referenced to decrease the instance count.
By moving the guts of __r into a temporary that is destroyed as the function returns, it is made sure that the moved-away-from object __r refers to is left in an empty state. I guess they wanted to keep that logic at one place, the move constructor, which looks like this.
__shared_ptr(__shared_ptr&& __r) noexcept
: _M_ptr(__r._M_ptr), _M_refcount()
{
_M_refcount._M_swap(__r._M_refcount);
__r._M_ptr = 0;
}
Personally, I prefer the following implementation which, to my best knowledge, is equivalent.
widget&
operator=(widget&& other) noexcept
{
widget temp {};
swap(*this, temp);
swap(*this, other);
return *this;
}
It can be complemented with a move constructor that is implemented like this.
widget(widget&& other) noexcept : widget {}
{
swap(*this, other);
}
I'm assuming that there is a
void
swap(widget&, widget&) noexcept;
overload that ADL can find and that the default-constructor of widget is noexcept.

Using swap inside assignment move operator

I c++ programming language 13.6.2 std::swap is used to implement move semantics the idea is below:
class deutscheSchweine{
public:
deutscheSchweine(){std::cout<<"DS\n";}
deutscheSchweine& operator=(const deutscheSchweine& other){
deutscheSchweine tmp;
swap(*this, tmp);
return *this;
}
deutscheSchweine(deutscheSchweine&& other){}
deutscheSchweine& operator=(deutscheSchweine&& other){
swap(*this, other);
return *this;
}
};
int main(){
deutscheSchweine ds;
deutscheSchweine ds2;
ds2 = ds;
I above example after calling assignment we can use move semantics to avid copying from temporary, but this example causes recursively calling move assignment. My question is can we use swap in move semantics but in some proper way?
Implementing copy-assignment via swap is a good idea, but you missed some of the details.
You need to call move on each of the individual members at some point. That can be done by calling swap(*this, other); and implementing a specialization of swap, by directly calling swap on each of the individual members, or by letting std::swap call your move assignment operator.
Move assignment should NOT be implemented using swap.
We already have an excellent guide to the "copy-and-swap" idiom, here: What is the copy-and-swap idiom?
Also read Should the Copy-and-Swap Idiom become the Copy-and-Move Idiom in C++11?
In the end, what you want (assuming your member objects are designed correctly) is:
class deutscheSchweine
{
public:
deutscheSchweine(){std::cout<<"DS\n";}
// defaulted move operations (member-wise moves)
deutscheSchweine(deutscheSchweine&& other) = default;
deutscheSchweine& operator=(deutscheSchweine&& other) = default;
// copy construction is defaulted (member-wise copies)
deutscheSchweine(const deutscheSchweine& other) = default;
// copy assignment uses copy-and-move for exception safety
deutscheSchweine& operator=(deutscheSchweine other)
{
return *this = std::move(other);
}
};

How does c++11 implements "... = default;" for the rule of three methods

When I learned C++ people told me to always implement at least rule of three methods.
Now I'm seeing the new "... = default;" from c++0x on stack overflow, and my question is:
Is there a c++11 standard implementation defined for those methods or is it compiler specific?
plus I would like to have some precisions:
What does the implementation looks like in term of code? (if it's generic)
Does this have an advantage compared to my example implementation below?
If you don't use assignment/copy constructor, what does *... = delete* do precisly, what's the difference with declaring them private? Answer (from #40two)
Is the new default= different from the old default implementation?
Disclaimer: when I'll need more advanced features in my methods, for sure I'll implements them myself. But I get used to implement assignment operator and copy constructor even when I never used them, just in order that the compiler don't.
What I used to do: (edited, #DDrmmr swap/move)
//File T.h
class T
{
public:
T(void);
T(const T &other);
T(const T &&other);
T &operator=(T other);
friend void swap(T &first, T &second);
~T(void);
protected:
int *_param;
};
//File T.cpp
T::T(void) :
_param(std::null)
{}
T::T(T &other)
: _param(other._param)
{}
T::T(T &&other)
: T()
{
swap(*this, other);
}
T &T::operator=(T other)
{
swap(*this, other);
return (*this);
}
friend void swap(T &first, T &second)
{
using std::swap;
swap(first._param, second._param);
}
T::~T(void)
{}
The default behavior is:
Default ctor ( T() ): calls bases def. ctors and members default ctors.
Copy ctor ( T(const T&) ): calls bases copy. ctors and members copy ctors.
Move ctor ( T(T&&) ): calls bases move. ctors and members move ctors.
Assign ( T& operator=(const T&) ): calls bases assign. and members assign.
Transfer ( T& operator=(T&&) ): calls bases transfer, and members transfer.
Destructor ( ~T() ): calls member destructor, and bases destructor (reverse order).
For built-in types (int etc.)
Default ctor: set to 0 if explicitly called
Copy ctor: bitwise copy
Move ctor: bitwise copy (no change on the source)
Assign: bitwise copy
Transfer: bitwise copy
Destructor: does nothing.
Since pointers are builtin types as well, this apply to int* ( not to what it points to).
Now, if you don't declare anything, your T class will just hold a int* that does not own the pointed int, so a copy of T will just hold a pointer to the same int. This is the same resulting behavior as C++03. Default implemented move for built-in types are copy. For classes are memberwise move (and depends on what members are: just copies for built-ins)
If you have to change this behavior, you have to do it coherently: for example, if you want to "own" what you point to, you need
a default ctor initializing to nullptr: this defines an "empty state" we can refer later
a creator ctor initializing to a given pointer
a copy ctor initializing to a copy of the pointed (this is the real change)
a dtor that deletes the pointed
an assign that deletes the pointed and receive a new copy of the pointed
.
T::T() :_param() {}
T::T(int* s) :_param(s) {}
T(const T& s) :_param(s._param? new int(*s._param): nullptr) {}
~T() { delete _param; } // will do nothing if _param is nullptr
Let's not define the assign, by now, but concentrate on the move:
If you don't declare it, since you declared the copy, it will be deleted: this makes a T object always being copied even if temporary (same behavior as c++03)
But if the source object is temporary, we can create an empty destination and swap them:
T::T(T&& s) :T() { std::swap(_param, s._param); }
This is what is called a move.
Now the assignment: before C++11 T& operator=(const T& s) should check against a self assignment, make the destination empty and receive a copy of the pointed:
T& operator=(const T& s)
{
if(this == &s) return *this; // we can shortcut
int* p = new int(s._param); //get the copy ...
delete _param; //.. and if succeeded (no exception while copying) ...
_param = p; // ... delete the old and keep the copy
return *this;
}
With C++11 we can use the parameter passing to generate the copy, thus giving
T& operator=(T s) //note the signature
{ std::swap(_param, s._param); return *this; }
Note that this works also in C++98, but the pass-by copy will not be optimized in pass-by move if s is temporary. This makes such implementation not profitable in C++98 and C++03 but really convenient in C++11.
Note also that there is no need to specialize std::swap for T: std::swap(a,b); will work, being implemented as three moves (not copy)
The practice to implement a swap function derives for the case where T has many members, being swap required in both move and assign. But it can be a regular private member function.

Should the Copy-and-Swap Idiom become the Copy-and-Move Idiom in C++11?

As explained in this answer, the copy-and-swap idiom is implemented as follows:
class MyClass
{
private:
BigClass data;
UnmovableClass *dataPtr;
public:
MyClass()
: data(), dataPtr(new UnmovableClass) { }
MyClass(const MyClass& other)
: data(other.data), dataPtr(new UnmovableClass(*other.dataPtr)) { }
MyClass(MyClass&& other)
: data(std::move(other.data)), dataPtr(other.dataPtr)
{ other.dataPtr= nullptr; }
~MyClass() { delete dataPtr; }
friend void swap(MyClass& first, MyClass& second)
{
using std::swap;
swap(first.data, other.data);
swap(first.dataPtr, other.dataPtr);
}
MyClass& operator=(MyClass other)
{
swap(*this, other);
return *this;
}
};
By having a value of MyClass as parameter for operator=, the parameter can be constructed by either the copy constructor or the move constructor. You can then safely extract the data from the parameter. This prevents code duplication and assists in exception safety.
The answer mentions you can either swap or move the variables in the temporary. It primarily discusses swapping. However, a swap, if not optimised by the compiler, involves three move operations, and in more complex cases does additional extra work. When all you want, is to move the temporary into the assigned-to object.
Consider this more complex example, involving the observer pattern. In this example, I've written the assignment operator code manually. Emphasis is on the move constructor, assignment operator and swap method:
class MyClass : Observable::IObserver
{
private:
std::shared_ptr<Observable> observable;
public:
MyClass(std::shared_ptr<Observable> observable) : observable(observable){ observable->registerObserver(*this); }
MyClass(const MyClass& other) : observable(other.observable) { observable.registerObserver(*this); }
~MyClass() { if(observable != nullptr) { observable->unregisterObserver(*this); }}
MyClass(MyClass&& other) : observable(std::move(other.observable))
{
observable->unregisterObserver(other);
other.observable.reset(nullptr);
observable->registerObserver(*this);
}
friend void swap(MyClass& first, MyClass& second)
{
//Checks for nullptr and same observable omitted
using std::swap;
swap(first.observable, second.observable);
second.observable->unregisterObserver(first);
first.observable->registerObserver(first);
first.observable->unregisterObserver(second);
second.observable->registerObserver(second);
}
MyClass& operator=(MyClass other)
{
observable->unregisterObserver(*this);
observable = std::move(other.observable);
observable->unregisterObserver(other);
other.observable.reset(nullptr);
observable->registerObserver(*this);
}
}
Clearly, the duplicated part of the code in this manually written assignment operator is identical to that of the move constructor. You could perform a swap in the assignment operator and the behaviour would be right, but it would potentially perform more moves and perform an extra registration (in the swap) and unregistration (in the destructor).
Wouldn't it make much more sense to reuse the move constructor's code in stead?
private:
void performMoveActions(MyClass&& other)
{
observable->unregisterObserver(other);
other.observable.reset(nullptr);
observable->registerObserver(*this);
}
public:
MyClass(MyClass&& other) : observable(std::move(other.observable))
{
performMoveActions(other);
}
MyClass& operator=(MyClass other)
{
observable->unregisterObserver(*this);
observable = std::move(other.observable);
performMoveActions(other);
}
It looks to me like this approach is never inferior to the swap approach. Am I right in thinking that the copy-and-swap idiom would be better off as the copy-and-move idiom in C++11, or did I miss something important?
First of all, it is generally unnecessary to write a swap function in C++11 as long as your class is movable. The default swap will resort to moves:
void swap(T& left, T& right) {
T tmp(std::move(left));
left = std::move(right);
right = std::move(tmp);
}
And that's it, the elements are swapped.
Second, based on this, the Copy-And-Swap actually still holds:
T& T::operator=(T const& left) {
using std::swap;
T tmp(left);
swap(*this, tmp);
return *this;
}
// Let's not forget the move-assignment operator to power down the swap.
T& T::operator=(T&&) = default;
Will either copy and swap (which is a move) or move and swap (which is a move), and should always achieve close to the optimum performance. There might be a couple redundant assignments, but hopefully your compiler will take care of it.
EDIT: this only implements the copy-assignment operator; a separate move-assignment operator is also required, though it can be defaulted, otherwise a stack overflow will occur (move-assignment and swap calling each other indefinitely).
Give each special member the tender loving care it deserves, and try to default them as much as possible:
class MyClass
{
private:
BigClass data;
std::unique_ptr<UnmovableClass> dataPtr;
public:
MyClass() = default;
~MyClass() = default;
MyClass(const MyClass& other)
: data(other.data)
, dataPtr(other.dataPtr ? new UnmovableClass(*other.dataPtr)
: nullptr)
{ }
MyClass& operator=(const MyClass& other)
{
if (this != &other)
{
data = other.data;
dataPtr.reset(other.dataPtr ? new UnmovableClass(*other.dataPtr)
: nullptr);
}
return *this;
}
MyClass(MyClass&&) = default;
MyClass& operator=(MyClass&&) = default;
friend void swap(MyClass& first, MyClass& second)
{
using std::swap;
swap(first.data, second.data);
swap(first.dataPtr, second.dataPtr);
}
};
The destructor could be implicitly defaulted above if desired. Everything else needs to be explicitly defined or defaulted for this example.
Reference: http://accu.org/content/conf2014/Howard_Hinnant_Accu_2014.pdf
The copy/swap idiom will likely cost you performance (see the slides). For example ever wonder why high performance / often used std::types like std::vector and std::string don't use copy/swap? Poor performance is the reason. If BigClass contains any std::vectors or std::strings (which seems likely), your best bet is to call their special members from your special members. The above is how to do that.
If you need strong exception safety on the assignment, see the slides for how to offer that in addition to performance (search for "strong_assign").
It's been a long time since I asked this question, and I've known the answer for a while now, but I've put off writing the answer for it. Here it is.
The answer is no. The Copy-and-swap idiom should not become the Copy-and-move idiom.
An important part of Copy-and-swap (which is also Move-construct-and-swap) is a way to implement assignment operators with safe cleanup. The old data is swapped into a copy-constructed or move-constructed temporary. When the operation is done, the temporary is deleted, and its destructor is called.
The swap behaviour is there to be able to reuse the destructor, so you don't have to write any cleanup code in your assignment operators.
If there's no cleanup behaviour to be done and only assignment, then you should be able to declare the assignment operators as default and copy-and-swap isn't needed.
The move constructor itself usually doesn't require any clean-up behaviour, since it's a new object. The general simple approach is to make the move constructor invoke the default constructor, and then swap all the members with the move-from object. The moved-from object will then be like a bland default-constructed object.
However, in this question's observer pattern example, that's actually an exception where you have to do extra cleanup work because references to the old object need to be changed. In general, I would recommend making your observers and observables, and other design constructs based around references, unmovable whenever possible.

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

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.
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.
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.
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.