Ответ 1
Существует несколько способов написать 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
? В этой области есть путаница.
До того, как С++ был стандартизирован, функции friend
сделали что-то, называемое "инъекция имени друга", где код вел себя так, как если бы функция была написана в окружающем пространстве имен. Например, это были эквивалентные предварительные стандарты:
struct foo
{
friend void bar()
{
// baz
}
};
// turned into, pre-standard:
struct foo
{
friend void bar();
};
void bar()
{
// baz
}
Однако, когда ADL был изобретен, это было удалено. Функция friend
может быть найдена только через ADL; если вы хотите, чтобы это была бесплатная функция, ее нужно было объявить так (см. это, например). Но вот! Возникла проблема.
Если вы просто используете std::swap(x, y)
, ваша перегрузка никогда не будет найдена, потому что вы явно сказали "посмотрите в std
, а нигде больше"! Вот почему некоторые люди предложили написать две функции: одну как функцию, которая будет найдена через ADL, а другая для обработки явных квалификаций std::
.
Но, как мы видели, это не может работать во всех случаях, и мы заканчиваем уродливым беспорядком. Вместо этого, идиоматическая свопинг пошла по другому маршруту: вместо того, чтобы сделать работу классов для предоставления std::swap
, это задача swappers, чтобы убедиться, что они не используют квалифицированный swap
, как указано выше. И это работает очень хорошо, если люди об этом знают. Но в этом и заключается проблема: неинтересно использовать неквалифицированный вызов!
Чтобы сделать это проще, некоторые библиотеки, такие как Boost, предоставили функцию boost::swap
, которая просто выполняет неквалифицированный вызов swap
, с std::swap
в качестве связанного пространства имен. Это помогает сделать вещи краткими снова, но это все еще облом.
Обратите внимание, что в С++ 11 нет изменений в поведении std::swap
, которые, как мы ошибочно думали, я и другие. Если вам было немного, читайте здесь.
Короче: функция-член - это просто шум, специализация уродливая и неполная, но функция friend
завершена и работает. И когда вы меняете, используйте boost::swap
или неквалифицированный swap
с std::swap
.
† Неформально имя ассоциируется, если оно будет рассмотрено во время вызова функции. Подробности см. В §3.4.2. В этом случае std::swap
обычно не рассматривается; но мы можем связать его (добавьте его в набор перегрузок, рассмотренных неквалифицированным swap
), позволяя его найти.