为什么需要使用std::move?

11

我读了一篇关于C++11中移动语义的精美文章。这篇文章以非常直观的方式写成。文章中给出了下面的示例类。

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

我错了哪里?


3
避免使用前导下划线可以避免意外使用保留名称,这是一个不错的主意。 - Mark B
1
养成好习惯很重要。在这个例子中,没有以大写字母开头,所以你没问题,但是你很容易忘记不要这样做。 - chris
@MarkB:这不是我的代码。我只是从参考文章中复制了它。 - PermanentGuest
2个回答

15

你的假设并不完全正确。构造函数的参数是一个xvalue,这允许将右值引用绑定到它,但一旦在构造函数中绑定了右值引用,它就不再是xvalue,而是lvalue。概念上,在调用处的对象正在“过期”,但在构造函数内部直到完成之前,它不再是“过期的”,因为它可以在构造函数块内稍后使用。

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

在[1]中,表达式f()指的是一个临时对象,该临时对象将在构造函数调用之后过期,因此可以通过rvalue引用进行绑定。

ArrayWrapper (ArrayWrapper&& other) 
    : _p_vals( other._p_vals  ) 
    , _metadata( other._metadata )        // [2] 
{ 
    other._p_vals = NULL; 
    std::cout << other._metadata << "\n"; // [3]
} 
在构造函数中,other不会过期,在构造函数的每个指令中都会存在。如果编译器允许[2]中的移动,则[3]中变量的潜在进一步使用将无效。您必须明确告诉编译器您希望该值立即失效。现在

好的,我想我明白了。这意味着,在[1]中的f()是一个xvalue,但是函数参数中的'other'不是xvalue,对吗? - PermanentGuest
3
@PermanentGuest:没错,一个绑定过的右值引用是一个左值。 - David Rodríguez - dribeas

9

other 是一个左值,因为它是一个变量。无论引用的类型是什么,命名引用都是左值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接