abraham_hilbert
abraham_hilbert

Reputation: 2271

Do we need move and copy assignment

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

Answers (3)

AnT stands with Russia
AnT stands with Russia

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:

    1. Move the right-hand side (RHS) value to the parameter other by using A's move constructor.
    2. Move the data from 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

Danh
Danh

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 noexcept, move should be lightweight, cheap, it isn't required but it should be lightweight. Hence, move constructor/assignment should be 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

Howard Hinnant
Howard Hinnant

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

Related Questions