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.