Існує кілька способів написання swap
, деякі кращі за інші. Однак з часом було виявлено, що одне визначення найкраще працює. Розглянемо, як ми могли б подумати над написанням swap
функції.
Спочатку ми бачимо, що такі контейнери std::vector<>
мають функцію члена з одним аргументом swap
, наприклад:
struct vector
{
void swap(vector&) { /* swap members */ }
};
Природно, тоді і наш клас повинен бути, правда? Ну не дуже. У стандартній бібліотеці є всілякі непотрібні речі , а член swap
- один із них. Чому? Давайте продовжимо.
Що ми повинні зробити, це визначити, що є канонічним та що наше заняття повинно зробити для роботи з ним. І канонічний метод заміни - с std::swap
. Ось чому функції членів не корисні: вони взагалі не так, як ми повинні міняти речі, і не мають жодного стосунку до поведінки std::swap
.
Ну тоді, щоб зробити std::swap
роботу, ми повинні забезпечити (і std::vector<>
повинні були забезпечити) спеціалізацію std::swap
, правда?
namespace std
{
template <> // important! specialization in std is OK, overloading is UB
void swap(myclass&, myclass&)
{
// swap
}
}
Добре, що, безумовно, спрацює в цьому випадку, але в ньому є яскрава проблема: спеціалізація функцій не може бути частковою. Тобто ми не можемо спеціалізувати шаблонні класи з цим, лише окремими моментами:
namespace std
{
template <typename T>
void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
{
// swap
}
}
Цей метод працює деякий час, але не весь час. Має бути кращий спосіб.
Є! Ми можемо використовувати friend
функцію та знаходити її через ADL :
namespace xyz
{
struct myclass
{
friend void swap(myclass&, myclass&);
};
}
Коли ми хочемо щось поміняти, ми пов’язуємо †, std::swap
а потім робимо некваліфікований дзвінок:
using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first
// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap
Що таке friend
функція? Навколо цього району існує плутанина.
Перш ніж стандартизувати C ++, friend
функції робили щось, що називалося "введення імені друга", де код поводився так, ніби функція була записана у навколишньому просторі імен. Наприклад, це були еквівалентні попередні стандарти:
struct foo
{
friend void bar()
{
// baz
}
};
// turned into, pre-standard:
struct foo
{
friend void bar();
};
void bar()
{
// baz
}
Однак коли було винайдено АДЛ, це було видалено. Потім friend
функцію можна було знайти лише через ADL; якщо ви хотіли це як вільну функцію, її потрібно було оголосити так ( див. це , наприклад). Але ось! Виникла проблема.
Якщо ви просто використовуєте std::swap(x, y)
, ваше перевантаження ніколи не буде знайдено, тому що ви прямо сказали «загляньте std
, і більше нікуди»! Ось чому деякі люди запропонували написати дві функції: одну як функцію, яку можна знайти через ADL , а другу для явної std::
кваліфікації.
Але, як ми бачили, це не може працювати у всіх випадках, і ми закінчуємо неприємний безлад. Натомість ідіоматичне обмінювання пішло іншим маршрутом: замість того, щоб зробити його завданням класів забезпечити std::swap
, це робота свопперів, щоб переконатися, що вони не використовують кваліфікованих swap
, як вище. І це, як правило, працює досить добре, доки люди знають про це. Але в цьому криється проблема: неінтуїтивно потрібно використовувати некваліфікований дзвінок!
Щоб полегшити це, деякі бібліотеки на зразок Boost надали функцію boost::swap
, яка просто виконує некваліфікований дзвінок swap
, std::swap
як асоційований простір імен. Це допомагає знову зробити лаконічні речі, але це все-таки облом.
Зауважте, що C ++ 11 у поведінці немає змін std::swap
, про що я та інші помилково вважали, що це буде так. Якщо вас це трохи покусало, читайте тут .
Коротше кажучи: функція члена - це просто шум, спеціалізація - потворна і неповна, але friend
функція завершена і працює. І коли ви обмінюєтесь, використовуйте boost::swap
або некваліфікуйте swap
з std::swap
асоційованим.
† Неофіційно ім'я асоціюється, якщо воно буде враховано під час виклику функції. Детальніше читайте §3.4.2. У цьому випадку std::swap
зазвичай це не враховується; але ми можемо пов’язати його (додати його до набору перевантажень, розглянутих некваліфікованими swap
), що дозволить його знайти.