I have seen it said that a operator=
written to take a parameter of the same type by-value serves as both copy assignment operator and move assignment operator in C++11:
Foo& operator=(Foo f)
{
swap(f);
return *this;
}
Where the alternative would be more than twice as many lines with a lot of code repetition, and potential for error:
Foo& operator=(const Foo& f)
{
Foo f2(f);
swap(f2);
return *this;
}
Foo& operator=(Foo&& f)
{
Foo f2(std::move(f));
swap(f2);
return *this;
}
In what circumstances is the ref-to-const and r-value overload preferable to
pass by value, or when is it necessary? I'm thinking about std::vector::push_back
,
for example which is defined as two overloads:
void push_back (const value_type& val);
void push_back (value_type&& val);
Following the first example where pass by value serves as copy assignment
operator and move assignment operator, couldn't push_back
be defined in
the Standard to be a single function?
void push_back (value_type val);
For types whose copy assignment operator can recycle resources, swapping with a copy is almost never the best way to implement the copy assignment operator. For example look at
std::vector
:This class manages a dynamically sized buffer and maintains both a
capacity
(maximum length the buffer can hold), and asize
(the current length). If thevector
copy assignment operator is implementedswap
, then no matter what, a new buffer is always allocated if therhs.size() != 0
.However, if
lhs.capacity() >= rhs.size()
, no new buffer need be allocated at all. One can simply assign/construct the elements fromrhs
tolhs
. When the element type is trivially copyable, this may boil down to nothing butmemcpy
. This can be much, much faster than allocating and deallocating a buffer.Same issue for
std::string
.Same issue for
MyType
whenMyType
has data members that arestd::vector
and/orstd::string
.There are only 2 times you want to consider implementing copy assignment with swap:
You know that the
swap
method (including the obligatory copy construction when the rhs is an lvalue) will not be terribly inefficient.You know that you will always need the copy assignment operator to have the strong exception safety guarantee.
If you're not sure about 2, in other words you think the copy assignment operator might sometimes need the strong exception safety guarantee, don't implement assignment in terms of swap. It is easy for your clients to achieve the same guarantee if you provide one of:
For example:
or:
Now there will be some types where implementing copy assignment with swap will make sense. However these types will be the exception, not the rule.
On:
Imagine
vector<big_legacy_type>
where:If we had only:
Then
push_back
ing an lvaluebig_legacy_type
into avector
would require 2 copies instead of 1, even whencapacity
was sufficient. That would be a disaster, performance wise.Update
Here is a HelloWorld that you should be able to run on any C++11 conforming platform:
I've coded
X
's copy assignment operator two ways:vector
's copy assignment operator.SLOW_DOWN
. I thought about naming itSLEEP_FOR_AWHILE
, but this way is actually much worse than sleep statements if you're on a battery powered device.The test constructs some randomly sized
vector<int>
s between 0 and 1000, and assigns them a million times. It times each one, sums the times, and then finds the average time in floating point nanoseconds and prints that out. If two consecutive calls to your high resolution clock doesn't return something less than 100 nanoseconds, you may want to raise the length of the vectors.Here are my results:
I'm seeing a 43% performance hit for the copy/swap idiom with this simple test. YMMV.
The above test, on average, has sufficient capacity on the lhs half the time. If we take this to either extreme:
then the performance advantage of the default copy assignment over the copy/swap idiom varies from about 560% to 0%. The copy/swap idiom is never faster, and can be dramatically slower (for this test).
Want Speed? Measure.