функция обмена друзей - PullRequest
       76

функция обмена друзей

143 голосов
/ 17 апреля 2011

В прекрасном ответе на 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 статично ? Очевидно, он не использует переменные-члены.
  • «При любом правильном использовании свопа своп будет обнаружен через ADL» ? ADL будет искать пространства имен, верно? Но это также смотрит на классы? Или здесь, куда входит friend?

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

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

Ответы [ 2 ]

152 голосов
/ 17 апреля 2011

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

7 голосов
/ 17 апреля 2011

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

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), имя функции является неквалифицированным, а функция имеет область имен.

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

...