В статье про swap идиому мы реализовали свап объектов класса через дружественную функцию. Сегодня поговорим почему так делать правильно.
В принципе нам нужна функциональность, которая сможет обменять данных двух объектов местами. Она может называться как угодно, никто не запрещает назвать вам swap функцию как TheMostTrickyFunction.
Но так делать не очень удобно. Все привыкли использовать std::swap для обмена значений. Поэтому логично как минимум назвать функцию swap.
Дальше будем рассматривать по очереди возможные варианты.
Можно определить метод swap внутри класса:
struct my_type
{
void swap(my_type&) { /* swap members / }
};
И хоть это будет работать в пользовательском коде just fine, но мы не сможем для такого типа например использовать std::sort, которая вызывает свободную функцию swap.
Но мы можем найти примеры такого дизайна даже в стандартной библиотеке. Например std::vector имеем метод swap, который обменивает данные двух векторов. Но тут важен контекст: до появления мув-семантики обмен векторов через std::swap приводило бы к нежелательным копированиям. Поэтому в те времена многие объекты имели свою оптимизированную версию в виде метода.
Сейчас вы можете использовать std::swap на двух векторах и не парится по поводу перфоманса. Так что просто swap метод класса нам не подходит.
Раз мы хотим использовать std::swap, то может тогда просто специализируем эту функцию для нашего типа в скоупе std? Давайте попробуем.
namespace std
{
template <>
void swap(my_type& one, my_type& two)
{
one.swap(two);
}
}
И это даже может и заработает. Но С++20 говорит нам, что специализировать шаблонные функции из неймспейса std - неопределенное поведение. Поэтому этот вариант - совсем не вариант.
Попробуем определение свободной функции swap в неймспейсе класса
namespace my_ns {
struct my_type
{
void swap(my_type&) { / swap members */ }
};
void swap( my_type<T> & lhs, my_type<T> & rhs ) noexcept
{
lhs.swap(rhs);
}
}Однако это выглядит просто как обертка для метода swap, который больше нигде не используется. Может как-то схлопнуть две эти сущности?
Сделаем эту свободную функцию дружественной нашему классу! Тогда можно выкинуть ненужный метод и оставить просто функцию.
struct my_type
{
friend void swap(my_type& first, my_type& second) noexcept {
// swap
}
};
Так-то лучше. Все работает и выглядит культурно. Далее поговорим про то, что должно быть внутри функции.
Be nice. Stay cool.
#template #cppcore #cpp20