If we're not lazy and define a swap function, we can use the copy/swap idiom to get a free pass on the copy-assignment operator. Not so for the move-assignment. This post from 2009 provides an interesting trick to reuse destructor/copy constructor to implement the copy-assignment without the swap function. Here's the code provided by that post:
struct A {
A ();
A (const A &a);
virtual A &operator= (const A &a);
virtual ~A ();
};
A &A::operator= (const A &a) {
if (this != &a) {
this->A::~A(); // explicit non-virtual destructor
new (this) A(a); // placement new
}
return *this;
}
The author does warn about the downsides of using this trick but I think the concerns are fairly minor. The technique can also be extended for the move-assignment operator:
template <typename T, typename U>
A &A::operator= (A &&a) {
if (this != &a) {
this->A::~A(); // explicit non-virtual destructor
new (this) A(std::move(a)); // placement new
}
return *this;
}
And can be generalized into a utility function that can cover both of those uses. The function and its usage is shown below:
template <typename T, typename U>
T& assign(T* obj, U&& other) {
if( obj != &other ) {
obj->T::~T();
new (static_cast<void*>(obj)) T(std::forward<U>(other));
}
return *obj;
}
struct A {
A ();
A (const A& a);
A(A&& a);
virtual ~A ();
A& operator= (const A& a) {
return assign(this, a);
}
A& operator=(A&& a) {
return assign(this, std::move(a));
}
};
One very nice side affect of this approach is that it becomes possible to create a copy/move assignment operators for those classes that can otherwise only be copy/move constructable. For example, consider:
struct X {
int& i;
};
The compiler will generate a copy/move constructor pair but will delete the corresponding assignment operators. You'd be hard pressed to define them yourself as well. But destroy/construct trick allows us to side step such limitations!
Note that assign's second argument is a "universal reference" and will bind to anything. Thus the assign function can actually by used to implement any assignment operator (not just copy/move) as long as the corresponding constructor is available.
Now suppose that struct X is located in a third party library and you don't want to modify it to add the assignment operators. By defining a utility class assignable<T>, we can add the desired functionality externally:
template <typename T>
class assignable : public T {
public:
using T::T;
assignable(T const& other) :
T(other) {
}
assignable(T&& other) :
T(std::move(other)) {
}
template <typename U>
assignable& operator=(U&& other) {
return assign(this, std::forward<U>(other));
}
};
// and usage:
struct X {
X(int& ii) : i(ii) {}
int& i;
};
int i = 1, j = 2;
assignable<X> x(i);
x = assignable<X>(j);
This approach makes me wonder if the language should support a way of auto generating the copy/move assignments not member-wise but from destructor and constructor. We could then opt-in to such goodness like this:
Foo& operator=(Foo&&) = default(via_constructor);
Foo& operator=(Foo const&) = default(via_constructor);
The code (along with a work around for compilers not supporting inheritable constructors) is available on GitHub.