Является ли специализация std::swap устаревшей, когда у нас есть семантика перемещения?
Возможный дубликат:
Переместить семантику == пользовательская функция подкачки устарела?
Вот как std::swap
выглядит как в C++11:
template<typename T>
void swap(T& x, T& y)
{
T z = std::move(x);
x = std::move(y);
y = std::move(z);
}
Нужно ли еще специализироваться std::swap
для моих собственных типов, или будет std::swap
быть настолько эффективным, насколько это возможно, при условии, что мой класс, конечно, определяет конструктор перемещения и оператор присваивания перемещения?
4 ответа
Специализация std::swap
теперь необязательно, но не рекомендуется. Обоснование - производительность.
Для прототипирования кода, и, возможно, даже для большого кода доставки, std::swap
будет достаточно быстро Однако, если вы находитесь в ситуации, когда вам нужно извлекать каждый кусочек из своего кода, написание пользовательского свопа все же может быть значительным преимуществом в производительности.
Рассмотрим случай, когда ваш класс по существу имеет один указатель-владелец, а ваш конструктор перемещения и присваивание перемещения просто должны иметь дело с этим одним указателем. Подсчитайте загрузку и количество машин для каждого участника:
Перемещение конструктора: 1 загрузка и 2 магазина.
Назначение перемещения: 2 груза и 2 магазина.
Пользовательский обмен: 2 загрузки и 2 магазина.
std::swap
1 конструкция хода и 2 назначения перемещения, или: 5 нагрузок и 6 магазинов.
Пользовательский своп потенциально еще в два-три раза быстрее, чем std::swap
, Хотя каждый раз, когда вы пытаетесь вычислить скорость чего-либо, подсчитывая нагрузки и запасы, оба будут быстро порочны.
Примечание. При расчете стоимости вашего задания на перемещение обязательно учитывайте, что вы будете переходить к значению перемещения из (в std::swap
алгоритм). Это часто сводит на нет стоимость сделки, хотя и за счет филиала.
Специализируется
std::swap
не рекомендуется теперь, когда у нас есть семантика перемещения?
Нет. Это общая версия, но вы можете оптимизировать ее, чтобы пропустить операцию третьего перемещения. Я предпочитаю сочетать идиомы копирования и замены с настройкой std::swap
для моих занятий.
Это означает, что у меня будет:
class Aaaa
{
public:
Aaaa(); // not interesting; defined elsewhere
Aaaa(Aaaa&& rvalueRef); // same
Aaaa(const Aaaa& ref); // same
~Aaaa(); // same
Aaaa& operator=(Aaaa object) // copy&swap
{
swap(object);
return *this;
}
void swap(Aaaa& other)
{
std::swap(dataMember1, other.dataMember1);
std::swap(dataMember2, other.dataMember2);
// ...
}
// ...
};
namespace std
{
template<> inline void std::swap(Aaaa& left, Aaaa& right)
{ left.swap(right); }
}
Это swap()
вызывает конструктор перемещения и 2 назначения перемещения. Я думаю, что можно написать более эффективно swap()
для его конкретного типа класса, как,
class X
{
int * ptr_to_huge_array;
public:
// ctors, assgn ops. etc. etc.
friend void swap(X& a, X& b)
{
using std::swap;
swap(a.ptr_to_huge_array, b.ptr_to_huge_array);
}
};
независимо от реализации конструктора перемещения и оператора присваивания.
Это будет зависеть от ваших типов.
Вы переместите его из x в z, из y в x, из z в y. Это три операции копирования базового представления (может быть, только один указатель, может быть, что-то еще, кто знает)
Теперь, возможно, вы можете создать более быстрый своп для вашего типа (трюк свопинга xor, встроенный ассемблер или, возможно, std::swap для ваших базовых типов просто быстрее).
Или, может быть, ваш компилятор хорошо умеет оптимизировать и по сути оптимизирует оба случая в одну и ту же инструкцию (например, иметь временную переменную в регистре).
Лично я склонен всегда реализовывать функцию-член подкачки, которая будет вызываться из нескольких мест, включая такие вещи, как назначение перемещения, но YMMV.