Переместить семантику == пользовательская функция подкачки устарела?
В последнее время появляется много вопросов о том, как предоставить 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_; }