Переместить семантику == пользовательская функция подкачки устарела? - PullRequest
31 голосов
/ 20 июня 2011

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

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

Ответы [ 5 ]

22 голосов
/ 20 июня 2011

Это вопрос суждения.Обычно я позволяю 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 на Tnoexcept.

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

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

std::swap<A> не исключено, даже без перемещения членов.

1 голос
/ 20 июня 2011

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

0 голосов
/ 15 августа 2017

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

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.

Custom swap не вызывает delete и, следовательно, может быть более эффективным. Я полагаю, что именно по этой причине std::unique_ptr предоставляет специализированную std::swap специализацию.

UPDATE

Кажется, что компиляторы Intel и Clang способны оптимизировать удаление нулевых указателей, а GCC - нет. См. Почему GCC не оптимизирует удаление нулевых указателей в C ++? для получения подробной информации.

UPDATE

Похоже, что с помощью 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_; }
0 голосов
/ 21 июня 2011

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

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

0 голосов
/ 21 июня 2011

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

...