Подтвердить что ты не робот

Is `x = std:: move (x)` undefined?

Пусть x - переменная некоторого типа, которая была ранее инициализирована. Является следующей строкой:

x = std::move(x)

undefined? Где это в стандарте и что он говорит об этом?

4b9b3361

Ответ 1

Нет, это не поведение undefined, это будет определенное поведение реализации, оно будет зависеть от того, как реализовано назначение переноса.

Соответственно этому LWG issue 2468: Self-move-назначение типов библиотек, обратите внимание, что это активная проблема и не имеет официального предложения, поэтому это должно считаются ориентировочными, а не окончательными, но он указывает разделы, которые участвуют в стандартной библиотеке, и указывает, что они в настоящее время конфликтуют. В нем говорится:

Предположим, что мы пишем

vector<string> v{"a", "b", "c", "d"};
v = move(v);

Каким должно быть состояние v? В стандарте ничего не сказано специфические для самодвижения-назначения. Там соответствующий текст в нескольких части стандарта, и неясно, как их согласовать.

[...]

Из текста не видно, как свести эти штуки вместе, потому что не ясно, какой из них имеет преимущество. Возможно, 17.6.4.9 [res.on.arguments] выигрывает (он налагает неявное предварительное условие, которое не упоминается в требованиях MoveAssignable, поэтому v = move (v) есть undefined), или, может быть, 23.2.1 [container.requirements.general] (он явно дает дополнительные гарантии для Container:: operator = за пределами того, что гарантировано для библиотечных функций в целом, поэтому v = move (v) является no-op) или, возможно, что-то еще.

В существующих реализациях, которые я проверил, для чего это стоит, v = move (v) появился, чтобы очистить вектор; он не оставил вектор неизменным и не вызвал крушения.

и предлагает:

Неофициально: измените таблицы требований MoveAssignable и Container (и любые другие таблицы требований, которые упоминают назначение переноса, если они есть), чтобы сделать его явным, что x = move (x) определено поведение и оно оставляет x в действительном, но неуказанном состоянии, Вероятно, это не то, что сегодня говорится в стандарте, но это, вероятно, то, что мы намеревались, и оно согласуется с тем, что мы сказали пользователям и с какими реализациями на самом деле.

Примечание. Для встроенных типов это в основном копия, мы можем видеть из стандартной секции С++ 14 5.17 [expr.ass]:

В простом присваивании (=) значение выражения заменяет значение объекта, на которое ссылается левый операнд.

который отличается от случая для классов, где 5.17 говорит:

Если левый операнд имеет тип класса, класс должен быть полным. Назначение объектам класса определено оператором присваивания копирования/перемещения (12.8, 13.5.3).

Примечание. clang имеет предупреждение о перемещении.

Ответ 2

Он вызывается X::operator = (X&&), поэтому для реализации этого случая требуется реализация (как это делается для X::operator = (const X&))

Ответ 3

Все, что делает, это вызов X::operator=(X&&) (с lvalue, квалифицированным "*this" ).

В примитивных типах std::move мало интересует и вообще не взаимодействует с =. Таким образом, это относится только к объектам типа класса.

Теперь для типа внутри std (или сгенерированного одним из его шаблонов) объекты move d из имеют тенденцию оставаться в неуказанном (но действительном) состоянии. Это не поведение undefined, но это не полезно.

Семантика каждого заданного X::operator=(X&&) должна быть рассмотрена, рассмотрение каждого типа внутри std будет "слишком широким" для ответа на переполнение стека. Они даже могут противоречить себе.

В общем случае, когда move ing от объекта, вы сообщаете потребителю, что "вам не важно, в каком состоянии находится объект после". Использование x = std::move(x) является таким образом невежливым, поскольку вы (как правило) заботитесь о том, какое состояние x находится после завершения операции (как вы его назначаете). Вы используете тот же объект, что и lvalue, и rvalue в пределах одной и той же операции, и это не является хорошей практикой.

Интересным исключением является значение по умолчанию std::swap, которое выглядит примерно так:

template<class T>
void swap(T& lhs, T& rhs) {
  T tmp = std::move(lhs);
  lhs = std::move(rhs);
  rhs = std::move(tmp);
}

средняя строка, lhs = std::move(rhs), делает x = std::move(x), если вы дважды вызываете swap на одном и том же объекте.

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