我在C ++ 11中的移动语义上读了一个beautiful article。本文以非常直观的方式编写。本文中的示例类如下所示。
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;
};
显然,在上面的移动构造函数实现中,嵌入元素_metadata
不会发生移动。为了实现这一点,诀窍是使用像这样的std::move()
方法。
ArrayWrapper (ArrayWrapper&& other)
: _p_vals( other._p_vals )
, _metadata( std::move( other._metadata ) )
{
other._p_vals = NULL;
}
到目前为止,非常好。
标准说:
§5(C ++11§5[expr] / 6):
[注意:表达式是xvalue,如果它是:
调用函数的结果,无论是隐式还是显式, 其返回类型是对象类型的右值引用,
转换为对象类型的右值引用,
指定非静态数据成员的类成员访问表达式 非引用类型,其中对象表达式是xvalue,或
.*
指向成员的表达式,其中第一个操作数是 xvalue和第二个操作数是指向数据成员的指针。
我的问题:
现在,移动构造函数中的变量other
是一个xvalue(我是对的吗?)。然后根据上面的最后一条规则,other._metadata
也应该是xvalue。因此编译器可以隐含地使用_metadata
类的移动构造函数。所以,这里不需要std::move
。
我错过了什么?
答案 0 :(得分:15)
你的假设不是真的。构造函数的参数是xvalue
,它允许绑定rvalue-reference,但是一旦绑定了rvalue-reference,在构造函数中,它不再是xvalue
而是{{{} 1}}。从概念上讲,调用位置的对象是 expiring ,但在构造函数内部直到它完成它不再是 expiring ,因为它可以在构造函数块中稍后使用。
lvalue
在[1]中,表达式ArrayWrapper f();
ArrayWrapper r = f(); // [1]
指的是一个临时的,在调用构造函数后将到期,因此它可以被rvalue-reference绑定。
f()
在构造函数中,ArrayWrapper (ArrayWrapper&& other)
: _p_vals( other._p_vals )
, _metadata( other._metadata ) // [2]
{
other._p_vals = NULL;
std::cout << other._metadata << "\n"; // [3]
}
没有到期,它对于构造函数的每个指令都是活动的。如果编译器允许在[2]中移动,则可能无法在[3]中进一步使用该变量。您必须明确告诉编译器您希望该值到期 now 。
答案 1 :(得分:9)
other
是一个左值,因为它是一个变量。命名引用是左值,无论它们是什么类型的引用。