В прошлый раз мы разобрались, почему swap лучше всего определять через дружественную функцию. Сегодня разберем то, что должно быть у нее внутри.
Очень хочется написать просто типа такого:
struct my_type
{
friend void swap(my_type& first, my_type& second) noexcept {
std::swap(first.member_1, second.member_1);
std::swap(first.member_2, second.member_2);
}
Type1 member1;
Type2 member2;
};
И это работает для тривиальных типов Type1, Type2. Если у вас тривиальные члены - пожалуйста пользуйтесь.
Но если Type1, Type2 - пользовательские типы не из стандартной библиотеки? Понятное дело, что для стдшных типов есть специализации std::swap. Но вот для обычных смертных - нет. И если для этого смертного типа определена своя функция swap, которая лучше знает, как обменивать данные, то таким образом вы никогда ее не вызовете. А если какой-то из ваших мемберов не удовлетворяет условию std::is_move_constructible_v<T> && std::is_move_assignable_v<T>, то вы вообще не скомпилируете этот код.
Выход - нужно как-то разрешить искать функцию swap в неймспейсе класса. Так как она более специализирована под ваш конкретный тип, то она будет предпочтительнее, чем стдшная, и будет вызываться вместо него.
Такая штука есть и называется она ADL или Argument Dependent Lookup. С ее помощью мы можем найти нужную функцию по типу аргумента. Можно написать:
struct my_type
{
friend void swap(my_type& first, my_type& second) noexcept {
swap(first.member_1, second.member_1);
swap(first.member_2, second.member_2);
}
Type1 member1;
Type2 member2;
};
И если для Type1 и Type2 определены свои функции swap, то они найдутся и все скомпилируется.
Однако выискивать для каждого типа своего мембера, есть ли у него своя собственная swap - задача неблагодарная. Хотелось бы и рыбку съесть и
struct my_type
{
friend void swap(my_type& first, my_type& second) noexcept {
using std::swap;
swap(first.member_1, second.member_1);
swap(first.member_2, second.member_2);
}
Type1 member1;
Type2 member2;
};
Какая тут магия произошла. Мы попрежнему используем ADL для поиска наиболее подходящей функции. Но если мы ее не нашли, то остается бэкап в виде std::swap, которая может вызваться благодаря using std::swap.
Примерно так и выглядит "каноничный" вид функции swap для ваших кастомных классов.
Sit on both chairs. Stay cool.
#cppcore