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

我错过了什么?

4

2 回答 2

15

你的假设不是真的。构造函数的参数是 an xvalue,它允许绑定右值引用,但是一旦绑定了右值引用,在构造函数内部,它就不再是 anxvalue而是 an lvalue。从概念上讲,调用位置的对象是expiring,但在构造函数内部并且在它完成之前它不再是expiring,因为它可以稍后在构造函数块中使用。

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

在 [1] 中,表达式f()指的是一个临时的,在调用构造函数后会过期,所以它可以被一个右值引用绑定。

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] 中的变量将是无效的。您必须明确告诉编译器您希望该值现在过期。

于 2012-07-30T15:37:17.447 回答
9

other是一个左值,因为它是一个变量。命名引用是左值,不管它们是什么类型的引用。

于 2012-07-30T15:32:25.680 回答