Является ли std:: swap (x, x) гарантированным оставить x неизменным?

Этот вопрос основан на обсуждении ниже недавнего блога Скотта Майера.

Кажется "очевидным", что std::swap(x, x) должен оставить x неизменным как в С++ 98, так и в С++ 11, но я не могу найти никаких гарантий этого эффекта ни в стандарте. С++ 98 определяет std::swap в терминах построения копии и назначения копии, тогда как С++ 11 определяет ее в терминах построения перемещения и назначения переноса, и это кажется уместным, потому что в С++ 11 (и С++ 14), 17.6.4.9 говорит, что назначение переноса не должно быть безопасным для самостоятельного назначения:

Если аргумент функции связывается с параметром ссылки rvalue, реализация может предположить, что этот параметр является уникальной ссылкой на этот аргумент.... [Примечание: если программа передает значение l xvalue при передаче этого lvalue в библиотечную функцию (например, вызывая функцию с аргументом move (x)), программа эффективно просит эту функцию относиться к этому lvalue как временному. реализация может свободно оптимизировать пропуски наложения псевдонимов, которые могут потребоваться, если аргумент был lvalue. -end note]

Отчет о дефекте , который породил эту формулировку, делает очевидным следующее:

это поясняет, что операторы присваивания перемещения не должны выполнять традиционный, если (этот!= & rhs) тест, обычно найденный (и необходимый) в операциях присваивания копий.

Но в С++ 11 и С++ 14 ожидается, что std::swap использует эту реализацию,

template<typename T>
void swap(T& lhs, T& rhs)
{
  auto temp(std::move(lhs));
  lhs = std::move(rhs);
  rhs = std::move(temp); 
}

и первое присваивание выполняет присваивание себе, где аргумент является значением r. Если оператор присваивания переходов для T следует политике стандартной библиотеки и не беспокоится о присвоении себе, это может показаться судом undefined, и это будет означать, что std::swap(x, x) будет иметь UB, а также.

Это вызывает беспокойство даже в изоляции, но если мы предположим, что std::swap(x, x) должен был быть безопасным в С++ 98, это также означает, что С++ 11/14 std::swap может молча сломать код С++ 98.

Итак, std::swap(x, x) гарантированно оставляет x неизменным? В С++ 98? В С++ 11? Если да, то как это взаимодействует с разрешением 17.6.4.9 для присваивания переадресации, чтобы он не был безопасным для самостоятельного назначения?

Ответы

Ответ 1

Ваше предположение о реализации С++ 11/14 std::swap неверно. В вашей версии temp делается копия, построенная из одного из аргументов; однако нет необходимости в этом. temp может быть перемещен, а остальное остается таким же, как ваш пример

template<typename T>
void swap(T& lhs, T& rhs) // omitting noexcept specification
{
  T temp = std::move(lhs);
  lhs = std::move(rhs);
  rhs = std::move(temp); 
}

Второй оператор по-прежнему является присваиванием self-move-присваиванием, но любой ресурс, принадлежащий lhsrhs), был перемещен в temp. Таким образом, оба объекта теперь должны находиться в "действительном, но неуказанном" состоянии, и назначение не должно вызывать никаких проблем на данный момент.

Изменить: Только что нашел этот ответ от Говарда Хиннанта, где он обсуждает эту ситуацию (около 2/3rds пути вниз). Его оценка такая же, как и выше.