Почему нужен 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, независимо от того, какова их ссылка.