Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why std::move is needed?

Tags:

c++

c++11

I read a beautiful article on the move semantics in C++11. This article is written in a very intuitive way. The example class in the article is given below.

class ArrayWrapper 
{ 
public: 
    // default constructor produces a moderately sized array 
    ArrayWrapper () 
        : _p_vals( new int[ 64 ] ) 
        , _metadata( 64, "ArrayWrapper" ) 
    {} 

    ArrayWrapper (int n) 
        : _p_vals( new int[ n ] ) 
        , _metadata( n, "ArrayWrapper" ) 
    {} 

    // move constructor 
    ArrayWrapper (ArrayWrapper&& other) 
        : _p_vals( other._p_vals  ) 
        , _metadata( other._metadata ) 
    { 
        other._p_vals = NULL; 
    } 

    // copy constructor 
    ArrayWrapper (const ArrayWrapper& other) 
        : _p_vals( new int[ other._metadata.getSize() ] ) 
        , _metadata( other._metadata ) 
    { 
        for ( int i = 0; i < _metadata.getSize(); ++i ) 
        { 
            _p_vals[ i ] = other._p_vals[ i ]; 
        } 
    } 
    ~ArrayWrapper () 
    { 
        delete [] _p_vals; 
    } 
private: 
    int *_p_vals; 
    MetaData _metadata; 
};

Clearly in the above move constructor implementation, the movement doesn't happen for the the embedded element _metadata. To facilitate this the trick is to use the std::move() method like this.

ArrayWrapper (ArrayWrapper&& other) 
        : _p_vals( other._p_vals  ) 
        , _metadata( std::move( other._metadata ) ) 
{ 
    other._p_vals = NULL; 
} 

So far, so good.

The standard says:

§5 (C++11 §5[expr]/6):

[ Note: An expression is an xvalue if it is:

  • the result of calling a function, whether implicitly or explicitly, whose return type is an rvalue reference to object type,

  • a cast to an rvalue reference to object type,

  • a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue, or

  • a .* pointer-to-member expression in which the first operand is an xvalue and the second operand is a pointer to data member.

My question:

Now, the variable other in the move constructor is an xvalue (am I right?). Then according to the last rule above, other._metadata should also be an xvalue. And hence the compiler can implicitely use the move constructor of _metadata's class. So, no need to std::move here.

What am I missing?

like image 959
PermanentGuest Avatar asked Jul 30 '12 15:07

PermanentGuest


People also ask

Why do we need move C++?

One of the most important concepts introduced in C++11 was move semantics. Move semantics is a way to avoid expensive deep copy operations and replace them with cheaper move operations. Essentially, you can think of it as turning a deep copy into a shallow copy.

What does move () do?

Move Constructor And Semantics: std::move() is a function used to convert an lvalue reference into the rvalue reference. Used to move the resources from a source object i.e. for efficient transfer of resources from one object to another.

What do std :: move and std :: forward do?

std::move takes an object and casts it as an rvalue reference, which indicates that resources can be "stolen" from this object. std::forward has a single use-case: to cast a templated function parameter of type forwarding reference ( T&& ) to the value category ( lvalue or rvalue ) the caller used to pass it.

Should I return std :: move?

std::move is totally unnecessary when returning from a function, and really gets into the realm of you -- the programmer -- trying to babysit things that you should leave to the compiler.


1 Answers

Your assumption is not really true. The argument to the constructor is an xvalue, which allows the rvalue-reference to be bound, but once the rvalue-reference is bound, inside the constructor, it is no longer an xvalue but an lvalue. Conceptually, the object at the call place is expiring, but inside the constructor and until it completes it is no longer expiring, as it can be used later within the constructor block.

ArrayWrapper f();
ArrayWrapper r = f();   // [1]

In [1], the expression f() refers to a temporary, that will expire after the call to the constructor, so it can be bound by an rvalue-reference.

ArrayWrapper (ArrayWrapper&& other) 
    : _p_vals( other._p_vals  ) 
    , _metadata( other._metadata )        // [2] 
{ 
    other._p_vals = NULL; 
    std::cout << other._metadata << "\n"; // [3]
} 

Inside the constructor, other is not expiring, it will be alive for each and every instruction of the constructor. If the compiler allowed moving in [2], than a potential further use of the variable in [3] would be invalid. You have to explicitly tell the compiler that you want the value to expire now.

like image 53
David Rodríguez - dribeas Avatar answered Oct 13 '22 06:10

David Rodríguez - dribeas