Почему существует так много специализаций std:: swap?
Рассматривая документацию для std::swap
, я вижу много специализаций.
Похоже, что каждый контейнер STL, а также многие другие объекты STD имеют специализированный обмен.
Я думал, что с помощью шаблонов нам не нужны все эти специализации?
Например,
Если я пишу свой собственный pair
, он корректно работает с шаблонизированной версией:
template<class T1,class T2>
struct my_pair{
T1 t1;
T2 t2;
};
int main() {
my_pair<int,char> x{1,'a'};
my_pair<int,char> y{2,'b'};
std::swap(x,y);
}
Итак, что получается из специализирования std::pair
?
template< class T1, class T2 >
void swap( pair<T1,T2>& lhs, pair<T1,T2>& rhs );
Мне также интересно, нужно ли писать собственные специализации для пользовательских классов,
или просто полагаться на версию шаблона.
Ответы
Ответ 1
Итак, что получается из специализации std:: pair?
Производительность. Обычно общий подкачка достаточно хороша (начиная с С++ 11), но редко оптимальна (для std::pair
и для большинства других структур данных).
Мне также интересно, следует ли мне писать собственные специализации для пользовательских классов или просто полагаться на версию шаблона.
Я предлагаю полагаться на шаблон по умолчанию, но если профилирование показывает, что это узкое место, знайте, что, вероятно, есть возможности для улучшения. Преждевременная оптимизация и все это...
Ответ 2
std::swap
реализуется по строкам кода ниже:
template<typename T> void swap(T& t1, T& t2) {
T temp = std::move(t1);
t1 = std::move(t2);
t2 = std::move(temp);
}
(см. "Как стандартная библиотека реализует std:: swap?" для получения дополнительной информации.)
Итак, что получается из специализирования std::pair
?
std::swap
может быть специализирован следующим образом (упрощенное от libc++
):
void swap(pair& p) noexcept(is_nothrow_swappable<first_type>{} &&
is_nothrow_swappable<second_type>{})
{
using std::swap;
swap(first, p.first);
swap(second, p.second);
}
Как вы можете видеть, swap
непосредственно вызывается в элементах пары с использованием ADL: это позволяет использовать настраиваемые и потенциально более быстрые реализации swap
для first
и second
(эти реализации могут использовать знание внутренней структуры элементов для большей производительности).
(см. "Как using std::swap
включить ADL?" для получения дополнительной информации.)
Ответ 3
Предположительно это по соображениям производительности в том случае, если типы pair
содержат дешево для свопинга, но дорого их копировать, например vector
. Поскольку он может вызывать swap на first
и second
вместо того, чтобы делать копию с временными объектами, это может обеспечить значительное улучшение производительности программы.
Ответ 4
Самый эффективный способ замены двух пар - это не то же самое, что наиболее эффективный способ обмена двумя векторами. Эти два типа имеют различную реализацию, разные переменные-члены и различные функции-члены.
Существует не просто общий способ "обменивать" два объекта таким образом.
Я имею в виду, что для типа с возможностью копирования вы можете это сделать:
T tmp = a;
a = b;
b = tmp;
Но это ужасно.
Для подвижного типа вы можете добавить некоторые std::move
и предотвратить копии, но тогда вам по-прежнему нужна семантика подкачки на следующем уровне, чтобы иметь полезную семантику перемещения. В какой-то момент вам нужно специализироваться.
Ответ 5
Причиной является производительность, особенно pre С++ 11.
Рассмотрим нечто вроде типа "Вектор". Вектор имеет три поля: размер, емкость и указатель на фактические данные. Он копирует конструктор и копирует копии фактических данных. Версия С++ 11 также имеет конструктор перемещения и перемещает назначение, которое крадет указатель, устанавливая указатель в исходном объекте на нуль.
Выделенная реализация векторного свопа может просто заменять поля.
Общая реализация свопинга на основе конструктора копирования, назначения копирования и деструктора приведет к копированию данных и распределению/освобождению динамической памяти.
Общая реализация свопа, основанная на конструкторе перемещения, назначении перемещения и деструкторе, позволит избежать копирования данных или распределения памяти, но это оставит некоторые избыточные обнуления и нулевые проверки, которые оптимизатор может или не сможет оптимизировать.
Итак, для чего нужна специализированная своп-реализация для "пары"? Для пары int и char нет необходимости. Они представляют собой простые старые типы данных, поэтому общий своп просто хорош.
Но что, если у меня есть пара слов Vector и String? Я хочу использовать специальные операции свопинга для этих типов, и поэтому мне нужна операция свопинга по типу пары, которая обрабатывает его, заменяя его компонентами компонента.
Ответ 6
Существует правило (я думаю, что это происходит либо с помощью программы Herb Sutter Exceptional С++, либо с помощью эффективной серии С++ для Скотта Мейера), если ваш тип может обеспечить реализацию swap, которая не бросает, или быстрее, чем общая std::swap
функция, он должен делать это как функция-член void swap(T &other)
.
Теоретически, общая функция std::swap()
может использовать магию шаблонов для обнаружения присутствия замены членов и вызова, вместо того, чтобы делать
T tmp = std::move(lhs);
lhs = std::move(rhs);
rhs = std::move(tmp);
но, похоже, никто не думал об этом, но люди склонны добавлять перегрузки бесплатных swap
для вызова (потенциально более быстрого) обмена членами.