Should I move the std::exchange ed members?

Viewed 993

The std::exchange can be used to impliment the move constructors. Here is an example from the cppreference.com https://en.cppreference.com/w/cpp/utility/exchange#Notes.

However, the possible implimenation of the std::exchange looks like this:

template<class T, class U = T>
T exchange(T& obj, U&& new_value)
{
    T old_value = std::move(obj);
    obj = std::forward<U>(new_value);
    return old_value;          // can be copy (until C++17) or a move (C++17), right?
}

Now my situation:

#include <string>
#include <utility>

struct MyClass
{
    std::string m_str;
    // some other non-primitive members of the class

    MyClass(MyClass&& other) : m_str{ std::exchange(other.m_str, {}) } // enough?
        // or
        // : m_str{ std::move(std::exchange(other.m_str, {})) }
        //          ^^^^^^^^^^    do i need to move?                          
    {}

    MyClass& operator=(MyClass&& other)
    {
        this->m_str = std::exchange(other.m_str, {}); // enough?
        // or 
        // this->m_str = std::move( std::exchange(other.m_str, {}) );
        //               ^^^^^^^^^^    do I need to move?   
        return *this;
    }
};

As I commented on the code, there is a chance for move or copy by the lines

m_str{ std::exchange(other.m_str, {}) }
this->m_str = std::exchange(other.m_str, nullptr);

Therefore,

  • should I use explicitly std::move for them, so that I can make sure that the members have been 100% moved to the other object?
  • If Yes, will it be more verbous to use std::exchange for this scenario?

I am using visual studio 2017 with compiler flag C++14.

1 Answers

No, using std::move is not necessary here. Rule of thumb is - if some returned value is not assigned to a variable it will be moved.

template<class T, class U = T>
T exchange(T& obj, U&& new_value)
{
    T old_value = std::move(obj);
    obj = std::forward<U>(new_value);
    return old_value;          // will be moved if move constructor defined
    // or even copy will be elided and will be no constructor call
}

The move is guaranteed here contrary to what you say. C++17 changes copy elision rules but that's something different

From here you can see that prvalue is:

function call or an overloaded operator expression, whose return type is non-reference, such as str.substr(1, 2), str1 + str2, or it++

And properties of prvalues (as subset of rvalues) are (emphasis mine):

An rvalue may be used to initialize an rvalue reference, in which case the lifetime of the object identified by the rvalue is extended until the scope of the reference ends.

When used as a function argument and when two overloads of the function are available, one taking rvalue reference parameter and the other taking lvalue reference to const parameter, an rvalue binds to the rvalue reference overload (thus, if both copy and move constructors are available, an rvalue argument invokes the move constructor, and likewise with copy and move assignment operators).

Related