Переместить семантику = = пользовательская функция swap устарела?
недавно много вопросы поп о том, как обеспечить собственную . С C++11, std::swap
использовать std::move
и переместите семантику, чтобы как можно быстрее поменять заданные значения. Это, конечно, работает, только если вы предоставляете конструктор перемещения и оператор назначения перемещения (или тот, который использует pass-by-value).
теперь, с учетом этого, на самом деле необходимо написать свой собственный swap
функции в C++11? Я мог только подумайте о неподвижных типах, но опять же, custom swap
s обычно работают через какой-то" обмен указателями " (он же перемещение). Может быть, с определенными ссылочными переменными? Хм...
5 ответов
это вопрос суда. Я обычно позволяю std::swap
выполните задание для прототипирования кода, но для кода выпуска напишите пользовательский своп. Обычно я могу написать пользовательский своп, который примерно в два раза быстрее, чем 1 move construction + 2 move assignments + 1 resourceless destruction. Однако можно подождать, пока std::swap
на самом деле оказывается проблемой производительности, прежде чем беспокоиться.
обновление для Alf P. Steinbach:
20.2.2 [утилита.своп] указывает, что std::swap(T&, T&)
есть noexcept
эквивалентно:
template <class T>
void
swap(T& a, T& b) noexcept
(
is_nothrow_move_constructible<T>::value &&
is_nothrow_move_assignable<T>::value
);
т. е. если переместить операции на T
are noexcept
, потом std::swap
on T
is noexcept
.
обратите внимание, что эта спецификация не требует от членов. Это требует только, чтобы конструкция и назначение из rvalues существовали, и если это noexcept
, тогда своп будет noexcept
. Например:
class A
{
public:
A(const A&) noexcept;
A& operator=(const A&) noexcept;
};
std::swap<A>
является noexcept, даже без членов move.
могут быть некоторые типы, которые можно поменять местами, но не перемещать. Я не знаю никаких неподвижных типов, поэтому у меня нет никаких примеров.
по соглашению обычай swap
предлагает гарантию не-хода. Я не знаю, о std::swap
. У меня сложилось впечатление, что работа комитета по этому вопросу носит политический характер, поэтому меня не удивило бы, если бы они где-то определили ... --2--> as bug
, или аналогичные политические маневры игры слов. Поэтому я бы не стал полагаться на ответ тут если он не обеспечивает подробный удар, ударяя цитатой из C++0x to-be-standard, до мельчайших деталей (чтобы быть уверенным, что нет bug
).
конечно, вы можете реализовать swap как
template <class T>
void swap(T& x, T& y)
{
T temp = std::move(x);
x = std::move(y);
y = std::move(temp);
}
но у нас может быть свой класс, скажем A
, который мы можем поменять более быстро.
void swap(A& x, A& y)
{
using std::swap;
swap(x.ptr, y.ptr);
}
который, вместо того, чтобы запускать конструктор и деструктор, просто меняет местами указатели (которые вполне могут быть реализованы как XCHG или что-то подобное).
конечно, компилятор может оптимизировать вызовы конструктора/деструктора в первом примере, но если у них есть побочные эффекты (т. е. вызовы new/delete), это возможно, недостаточно умен, чтобы оптимизировать их.
рассмотрим следующий класс, который содержит выделенный для памяти ресурс (для простоты, представленный одним целым числом):
class X {
int* i_;
public:
X(int i) : i_(new int(i)) { }
X(X&& rhs) noexcept : i_(rhs.i_) { rhs.i_ = nullptr; }
// X& operator=(X&& rhs) noexcept { delete i_; i_ = rhs.i_;
// rhs.i_ = nullptr; return *this; }
X& operator=(X rhs) noexcept { swap(rhs); return *this; }
~X() { delete i_; }
void swap(X& rhs) noexcept { std::swap(i_, rhs.i_); }
};
void swap(X& lhs, X& rhs) { lhs.swap(rhs); }
затем std::swap
результаты удаление нулевого указателя 3 раза (как двигаться оператор присваивания и объединяющий оператор присваивания случаях). Компиляторы могут иметь проблемы с оптимизацией такого delete
см. https://godbolt.org/g/E84ud4.
таможня swap
не вызывает каких-либо delete
и, следовательно, может быть более эффективным. Я думаю, это причина, почему std::unique_ptr
обеспечивает пользовательскую std::swap
специализация.
обновление
кажется, что компиляторы Intel и Clang могут оптимизировать удаление нулевых указателей, однако GCC нет. См.почему GCC не оптимизирует удаление нулевых указателей в C++? для сведения.
обновление
это кажется, что с GCC мы можем предотвратить вызов delete
оператора путем переписывания X
следующим образом:
// X& operator=(X&& rhs) noexcept { if (i_) delete i_; i_ = rhs.i_;
// rhs.i_ = nullptr; return *this; }
~X() { if (i_) delete i_; }