In C++, copy-swap idiom is typically implemented like this:
C& operator=(C rhs)
{
swap(*this, rhs);
return *this;
}
Now, if I want to add a move-assignment operator, it is supposed to look like this:
C& operator=(C&& rhs)
{
swap(*this, rhs);
return *this;
}
However, this creates ambiguity about which assignment operator should be called and compilers rightfully complain about it. So my question is the following: If I want to support copy-swap idiom together with move-assignment semantics what am I supposed to do?
Or is this a non-issue as having a move-copy constructor and copy-swap idiom, one doesn't really benefit from having a move-assignment operator?
After asking this question, I've written a code that demonstrates that move-assignment may result in fewer function calls than copy-swap idiom. First let me present my copy-swap version. Please bear with me; it appears like a long but a simple example:
#include <algorithm>
#include <iostream>
#include <new>
using namespace std;
bool printOutput = false;
void* operator new(std::size_t sz)
{
if (printOutput)
{
cout << "sz = " << sz << endl;
}
return std::malloc(sz);
}
class C
{
int* data;
public:
C() : data(nullptr)
{
if (printOutput)
{
cout << "C() called" << endl;
}
}
C(int data) : data(new int)
{
if (printOutput)
{
cout << "C(data) called" << endl;
}
*(this->data) = data;
}
C(const C& rhs) : data(new int)
{
if (printOutput)
{
cout << "C(&rhs) called" << endl;
}
*data = *(rhs.data);
}
C(C&& rhs) : C()
{
if (printOutput)
{
cout << "C(&&rhs) called" << endl;
}
swap(*this, rhs);
}
C& operator=(C rhs)
{
if (printOutput)
{
cout << "operator= called" << endl;
}
swap(*this, rhs);
return *this;
}
C operator+(const C& rhs)
{
C result(*data + *(rhs.data));
return result;
}
friend void swap(C& lhs, C& rhs);
~C()
{
delete data;
}
};
void swap(C& lhs, C& rhs)
{
std::swap(lhs.data, rhs.data);
}
int main()
{
C c1(7);
C c2;
printOutput = true;
c2 = c1 + c1;
return 0;
}
I have compiled this with g++ using the -fno-elide-constructors option as I want to see the no optimization behavior. The result is the following:
sz = 4
C(data) called // (due to the declaration of result)
C() called // (called from the rvalue copy-constructor)
C(&&rhs) called // (called due to copy to return temporary)
C() called // (called from the rvalue copy-constructor)
C(&&rhs) called // (called due to pass-by-value in the assignment operator)
operator= called
Now, if I choose not to make copy-swap idiom in the assignment operator, I will have something like this:
C& operator=(const C& rhs)
{
if (printOutput)
{
cout << "operator=(const C&) called" << endl;
}
if (this != &rhs)
{
delete data;
data = new int;
*data = *(rhs.data);
}
return *this;
}
This allows me to have the move-assignment operator as follows:
C& operator=(C&& rhs)
{
if (printOutput)
{
cout << "operator=(C&&) called" << endl;
}
swap(*this, rhs);
return *this;
}
Now, with everything else being the same, I get the following output:
sz = 4
C(data) called // (due to the declaration of result)
C() called // (called from the rvalue copy-constructor)
C(&&rhs) called // (called due to copy to return temporary)
operator=(C&&) called // (move-assignment)
As you can see this results in fewer function calls. Actually the last three function calls in the copySwapIdiom has now dropped down to a single function call. This is expected as we no longer pass the assignment operator parameter by value, hence no construction happens there.
However, I do not benefit from the beauty of copy-swap idiom in the assignment operator. Any insight is much appreciated.
There actually isn't a need to implement the move assignment operator if you provide a valid move constructor.
class Foo
{
public:
explicit Foo(Bar bar)
: bar(bar)
{ }
Foo(const Foo& other)
: bar(other.bar)
{ }
Foo(Foo&& other)
: bar(other.bar)
{ }
// other will be initialized using the move constructor if the actual
// argument in the assignment statement is an rvalue
Foo& operator=(Foo other)
{
std::swap(bar, other.bar);
return *this;
}
The motivation behind the copy-swap idiom here is to forward the copy/move work to constructors, so that you don't duplicate work for both constructors and assignment operators. That said,
C& operator=(C rhs) noexcept;
means to replace the pair
C& operator=(const C& rhs);
C& operator=(C&& rhs) noexcept;
Whether C& operator=(C rhs) noexcept;
performs copy or move assignment depends on how rhs
is constructed. For example,
a = std::move(b); // rhs is move-constructed from r-value std::move(b), and thus move-assignment
c = d; // rhs is copy-constructed from l-value d, and thus copy-assignment
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With