Есть несколько способов написать 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
), что позволит его найти.
person
GManNickG
schedule
17.04.2011
friend
вообще не является функцией-членом. - person aschepler   schedule 09.08.2016