Есть несколько способов написать 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
}
Однако, когда ADL был изобретен, это было удалено. 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
), что позволит его найти.