Reputation: 2271
For a class A
, can we use
A& operator=( A other ) { /* ... */ }
instead of
A& operator=( const A& other ) { /* ... */ }
A& operator=( const A&& other ) { /* ... */ }
without worsening performance or other negative effects?
Upvotes: 3
Views: 598
Reputation: 320797
Performance of "move" contexts might suffer slightly.
If your class A
is movable, then in moving contexts the pass-by-value copy-and-swap implementation will implement move as a sequence of two operations:
other
by using A
's move constructor.other
to *this
(or swap other
with *this
).The second implementation (with dedicated move-assignment) can do the same thing in just one operation: it will move (or swap) the right-hand side value with *this
directly.
So, potentially, the copy-and-swap variant involves the overhead of one extra move.
Performance of "copy" contexts can suffer from slightly to severely.
If your left-hand side (LHS) has some copyable internal resource (like a memory block) that can accommodate the corresponding RHS value without reallocation, then a dedicated implementation of copy-assignment just needs to copy the data from RHS resource to LHS resource. No memory allocation is necessary.
In copy-and-swap variant the copy is created unconditionally and the aforementioned internal resource has to be allocated and deallocated unconditionally. This can have a major negative performance impact.
Upvotes: 4
Reputation: 6016
Actually, you can implement copy-and-swap with
A& operator=( A other ) { swap(other, *this); return *this; }
for both copy and move assignment but the unconditional copy for self-assignment operator will downgrade performance.
Except, above function can't be marked move should be lightweight, cheap, it isn't required but it should be lightweight. Hence, move constructor/assignment should be noexcept
,noexcept
. without noexcept
move constructor/assignment operator, move_if_noexcept
can't be called when the container grows, the container will go back to copy.
Hence, the recommended way is:
A& operator=( const A& other ) {
if (std::addressof(other) != this) {
A(other).swap(*this); // Copy constructor may throw
}
return *this;
}
A& operator=( A&& other ) noexcept { // note no const here
// we don't check self assignment because
// an object binds to an rvalue reference it is one of two things:
// - A temporary.
// - An object the caller wants you to believe is a temporary.
swap(other);
return *this;
}
Upvotes: 6
Reputation: 219588
without worsening performance or other negative effects?
It depends on the data members and base classes of A
, and can even depend on the bases and members of A's bases and members.
Here is a link to a part of a video that demonstrates that if A
has a vector
as a data member, then the copy/swap idiom can cost a 70% performance hit for the copy assignment operator on average. This is compared to the much easier approach of defaulting the assignment special members.
http://www.youtube.com/watch?v=vLinb2fgkHk&t=35m30s
class A
{
std::vector<int> v_;
public:
// ...
};
You can expect string
data members to have the same impact as vector
data members.
If you aren't sure for your A
, and if performance is important to you, try it both ways and measure. This is precisely what is demonstrated in the video.
Upvotes: 5