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

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

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

5 ответов

Решение

Это вопрос суждения. Я обычно позволю std::swap сделать работу для прототипирования кода, но для кода выпуска написать собственный своп. Обычно я могу написать собственный своп, который примерно в два раза быстрее, чем 1 ход строительства + 2 задания хода + 1 безресурсное уничтожение. Однако можно подождать, пока std::swap на самом деле оказывается проблема производительности, прежде чем идти на беспокойство.

Обновление для Альфа П. Штайнбаха:

20.2.2 [utility.swap] указывает, что 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 являются noexcept, затем std::swap на T является noexcept,

Обратите внимание, что эта спецификация не требует перемещения членов. Требуется только, чтобы существовала конструкция и присвоение из rvalues, и если это noexceptтогда своп будет noexcept, Например:

class A
{
public:
    A(const A&) noexcept;
    A& operator=(const A&) noexcept;
};

std::swap<A> не исключая даже членов движения.

Конечно, вы можете реализовать своп как

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), он может быть недостаточно умен, чтобы их оптимизировать.

Могут быть некоторые типы, которые можно поменять местами, но не переместить. Я не знаю ни одного неподвижного типа, поэтому у меня нет примеров.

По обычаю обычай swap предлагает гарантию без броска. Я не знаю о std::swap, Мое впечатление о работе комитета по этому вопросу состоит в том, что все это было политическим, поэтому меня не удивило бы, если бы они где-то определили duck как bugили аналогичные политические маневры. Так что я бы не стал полагаться на какой-либо ответ здесь, если только он не предоставит подробные ударные цитаты из стандартного C++0x, вплоть до мельчайших деталей (чтобы быть уверенным, что нет bug).

Рассмотрим следующий класс, который содержит выделенный памяти ресурс (для простоты представлен одним целым числом):

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_; }
Другие вопросы по тегам