std::move

  • std::move unconditionally casts its input to rvalue reference.
  • std::move does not move anything
template <typename T>
std::remove_reference_t<T>&& move(T&& t) noexcept
{
    return static_cast<std::remove_reference_t<T>&&>( t );
} 

std::move is semantic transfer of ownership.

lvalue and rvalue

lvalue: Some value that has a name
rvalue: Value with no name.

std::string s;
s + s = s

The above code is perfectly valid. s+s is a rvalue and s is a lvalue inspite of beign on the opposite side of the = operator.


Move Constructor

In the move constructor all the members are moved explicitly using their move constructor. If we do not do so, the move constructor will only copy the variables.

For the pointers, when we perform the move operation, we need to set the source pointer to nullptr. We use std::exchange.

std::exchange

template< class T, class U = T >
T exchange( T& obj, U&& new_value );
constexpr T exchange( T& obj, U&& new_value )
  • Replaces the value of obj with new_value and returns the old value of obj.

Example of Move constructor

class Widget {
    private:
        int i{0};
        std::string s{};
        int *p{ nullptr };
        
   ` public:
        // Move Constructor
        Widget(Widget&& w) noexcept
            :i (std::move(w.i))
            ,s (std::move(w.s))
            ,p (std::exchange(w.pi, nullptr))
        {
        }

}
  • The constructor is noexcept. It means if the constructor fails, it will behave as no operation was performed. The noexcept is for the speed up of the constructor. The speed up due to this is 60%.
  • C++ Core guidelin: Ideally that is moved-from should be set to the default value type. If its not set there must be a very strong reason to do so.
    In the above implementation when i is moved its value after moving should be defaulted to 0.

If the move constructor is defaulted then the moved-from values have undefined state.

    // No need to rest the unique pointer
    std::unique_ptr<int> p{};
    Widget(Widget&& w) noexcept
        :i (std::move(w.i))
        ,s (std::move(w.s))
        ,p (std::move(w.p));
    { }
    

    // The above constructor can be defaulted.
    // Default is also no except.
    Widget(Widget&& w) = default;

Move assignment operator

  • Clean up all visible resources
  • Transfer the contents of w into this.
  • Leave w in a valid but undefined state.
Widget operator=(Widget&& w)
{
    // free the current resource
    delete pi;

    i = std::move(w.i);
    s = std::move(s.i);

    // below can be replaced by std::exchange
    pi = std::move(w.pi)=;
    w.pi = nullptr;

    return *this;
}
  • The current pointer may be holding some resource. If we perform move operation, then the resource is lost and never evicted from the memory. Therefore delete the pointer and then perform the move operation.
// Takes rvalues
vector&  operator=(vector&& rhs )

std::vector v1{};
std::vector v2{1,2,3};

/* 
* std::move() converts lvalue to Xvalue
* Xvalue means rvalue that expires.
*/
v1 = std::move(v2);

Here the v2 becomes invalid when its moved to v1, but its still alive. The copy assignment can again make v2 valid.