Подтвердить что ты не робот

Почему существует так много специализаций 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 );

Мне также интересно, нужно ли писать собственные специализации для пользовательских классов,
или просто полагаться на версию шаблона.

4b9b3361

Ответ 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 для вызова (потенциально более быстрого) обмена членами.