Reputation: 5331
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?
Upvotes: 11
Views: 750
Reputation: 208353
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.
Upvotes: 15
Reputation: 234474
other
is an lvalue, because it's a variable. Named references are lvalues, regardless of what kind of reference they are.
Upvotes: 9