Функция обмена друзьями

В прекрасном ответе на идиому copy-and-swap- a есть фрагмент кода, который мне нужно немного помочь:

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        using std::swap; 
        swap(first.mSize, second.mSize); 
        swap(first.mArray, second.mArray);
    }
    // ...
};

и он добавляет записку

Существуют и другие утверждения, что мы должны специализировать std::swap для нашего типа, обеспечивать обмен в классе вместе со свободным обменом функций и т. Д. Но это все не нужно: любое правильное использование swap будет осуществляться через неквалифицированный вызов и наша функция будет найдена через ADL. Одна функция будет делать.

С friend Я немного в "недружественных" отношениях, должен признать. Итак, мои основные вопросы:

  • выглядит как свободная функция, но внутри тела класса?
  • почему не это swapстатический? Очевидно, он не использует переменные-члены.
  • "Любое правильное использование свопа будет определять своп через ADL"? ADL будет искать пространства имен, верно? Но это также смотрит на классы? Или здесь, гдеfriendприходит в?

Побочные вопросы:

  • С C++11 я должен отметить мойswapсnoexcept?
  • С C++11 и его диапазоном, я должен разместить friend iter begin() а также friend iter end() так же внутри класса? я думаю friend здесь не нужно, верно?

2 ответа

Решение

Есть несколько способов написать 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), позволяя его найти.

Этот код эквивалентен (почти во всех отношениях):

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second);
    // ...
};

inline void swap(dumb_array& first, dumb_array& second) // nothrow
{
    using std::swap; 
    swap(first.mSize, second.mSize); 
    swap(first.mArray, second.mArray);
}

Функция друга, определенная внутри класса:

  • помещается в замкнутое пространство имен
  • автоматически inline
  • может ссылаться на статических членов класса без дальнейшей квалификации

Точные правила в разделе [class.friend] (Я цитирую пункты 6 и 7 проекта C++0x):

Функция может быть определена в объявлении друга класса тогда и только тогда, когда класс является нелокальным классом (9.8), имя функции является неквалифицированным, а функция имеет область пространства имен.

Такая функция неявно встроенная. Функция-друг, определенная в классе, находится в (лексической) области действия класса, в котором она определена. Функция друга, определенная вне класса, не является.

Другие вопросы по тегам