Является ли 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-присваиванием, но любой ресурс, принадлежащий lhs
(и rhs
), был перемещен в temp
. Таким образом, оба объекта теперь должны находиться в "действительном, но неуказанном" состоянии, и назначение не должно вызывать никаких проблем на данный момент.
Изменить: Только что нашел этот ответ от Говарда Хиннанта, где он обсуждает эту ситуацию (около 2/3rds пути вниз). Его оценка такая же, как и выше.