Переместить семантику = = пользовательская функция swap устарела?

недавно много вопросы поп о том, как обеспечить собственную . С C++11, std::swap использовать std::move и переместите семантику, чтобы как можно быстрее поменять заданные значения. Это, конечно, работает, только если вы предоставляете конструктор перемещения и оператор назначения перемещения (или тот, который использует pass-by-value).

теперь, с учетом этого, на самом деле необходимо написать свой собственный swap функции в C++11? Я мог только подумайте о неподвижных типах, но опять же, custom swaps обычно работают через какой-то" обмен указателями " (он же перемещение). Может быть, с определенными ссылочными переменными? Хм...

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_; }