Почему нужен std:: move?

Я прочитал прекрасную статью о семантике перемещения в С++ 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 (С++ 11 §5 [expr]/6):

[Примечание: выражение представляет собой значение x, если оно:

  • результат вызова функции, неявно или явно, чей тип возврата является ссылкой rvalue на тип объекта,

  • приведение к ссылке rvalue на тип объекта,

  • выражение доступа к члену класса, обозначающее нестатический элемент данных не ссылочного типа, в котором выражение объекта является значением x, или

  • a .* выражение "указатель-к-член", в котором первый операнд является xvalue, а второй операнд - указатель на элемент данных.

Мой вопрос:

Теперь переменная other в конструкторе перемещения является xvalue (я прав?). Тогда, согласно последнему правилу выше, other._metadata также должно быть значением x. И, следовательно, компилятор может неявно использовать конструктор перемещения класса _metadata. Поэтому здесь не нужно std::move.

Что мне не хватает?

Ответ 1

Ваше предположение на самом деле не так. Аргументом для конструктора является xvalue, который позволяет привязывать rvalue-ссылку, но после привязки rvalue-ссылки внутри конструктора он больше не является 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] было бы неверным. Вы должны явно сообщить компилятору, что вы хотите, чтобы значение истекало сейчас.

Ответ 2

other является lvalue, потому что это переменная. Именованные ссылки являются значениями lvalues, независимо от того, какова их ссылка.