Функция обмена членами публичного друга

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

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 static? Очевидно, что он не использует никаких переменных-членов.
  • "Любое правильное использование swap обнаружит swap через ADL" ? ADL будет искать пространства имен, не так ли? Но разве он также выглядит внутри классов? Или здесь, где friend входит?

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

  • С С++ 11, следует ли отмечать мой swap noexcept?
  • С С++ 11 и его range-for, должен ли я помещать friend iter begin() и friend iter end() таким же образом внутри класса? Я думаю, что friend здесь не нужен, правильно?
4b9b3361

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

114
ответ дан 17 апр. '11 в 22:24
источник

Этот код эквивалентен (почти всеми способами):

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 проекта С++ 0x):

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

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

4
ответ дан 17 апр. '11 в 21:41
источник