Как перегружать std:: swap()

std::swap() используется многими контейнерами std (такими как std::list и std::vector) во время сортировки и четного присваивания.

Но реализация std swap() очень обобщена и довольно неэффективна для пользовательских типов.

Таким образом, эффективность может быть достигнута за счет перегрузки std::swap() с помощью специфичной для конкретного типа реализации. Но как вы можете реализовать его, чтобы он использовался контейнерами std?

Ответ 1

Правильный способ перегрузки swap состоит в том, чтобы записать его в том же пространстве имен, что и в том, что вы меняете, чтобы его можно было найти через зависящий от аргумента поиск (ADL). Особенно легко сделать следующее:

class X
{
    // ...
    friend void swap(X& a, X& b)
    {
        using std::swap; // bring in swap for built-in types

        swap(a.base1, b.base1);
        swap(a.base2, b.base2);
        // ...
        swap(a.member1, b.member1);
        swap(a.member2, b.member2);
        // ...
    }
};

Ответ 2

Внимание Mozza314

Ниже приведено симуляция эффектов общего вызова std::algorithm std::swap и предоставления пользователю их свопинга в пространстве имен std. Поскольку это эксперимент, это моделирование использует namespace exp вместо namespace std.

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            exp::swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

namespace exp
{
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

Для меня это печатает:

generic exp::swap

Если ваш компилятор печатает что-то другое, то он неправильно реализует "двухфазный поиск" для шаблонов.

Если ваш компилятор соответствует (любому из С++ 98/03/11), он даст тот же результат, который я покажу. И в этом случае точно то, что вы боитесь, произойдет. И размещение вашего swap в пространстве имен std (exp) не остановило его.

Дэйв и я оба члены комитета и работаем в этой области стандарта на протяжении десятилетия (и не всегда соглашаемся друг с другом). Но этот вопрос давно урегулирован, и мы оба согласны с тем, как он был урегулирован. Не обращайте внимания на мнение Дейва/ответ в этой области на свой страх и риск.

Эта проблема возникла после публикации С++ 98. Начиная с 2001 года мы с Дейвом начали работать в этой области. И это современное решение:

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

void swap(A&, A&)
{
    printf("swap(A, A)\n");
}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

Выход:

swap(A, A)

Обновление

Было сделано замечание о том, что:

namespace exp
{    
    template <>
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

работает! Так почему бы не использовать это?

Рассмотрим случай, когда ваш A является шаблоном класса:

// simulate user code which includes <algorithm>

template <class T>
struct A
{
};

namespace exp
{

    template <class T>
    void swap(A<T>&, A<T>&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A<int> a[2];
    exp::algorithm(a, a+2);
}

Теперь это не работает.: - (

Итак, вы можете поместить swap в пространство имен std и заставить его работать. Но вам нужно будет запомнить swap в пространстве имен A для случая, когда у вас есть шаблон: A<T>. И поскольку оба случая будут работать, если вы поместите swap в пространство имен A, просто запомнить (и научить других) просто сделать это одним способом.

Ответ 3

Вам запрещено (по стандарту С++) перегружать std:: swap, однако вам специально разрешено добавлять специализированные типы шаблонов для ваших собственных типов в пространство имен std. Например.

namespace std
{
    template<>
    void swap(my_type& lhs, my_type& rhs)
    {
       // ... blah
    }
}

то использование в контейнерах std (и в другом месте) будет определять вашу специализацию вместо общей.

Также обратите внимание, что предоставление реализации swap-реализации базового класса недостаточно для ваших производных типов. Например. если у вас есть

class Base
{
    // ... stuff ...
}
class Derived : public Base
{
    // ... stuff ...
}

namespace std
{
    template<>
    void swap(Base& lha, Base& rhs)
    {
       // ...
    }
}

это будет работать для базовых классов, но если вы попытаетесь обменять два объекта Derived, он будет использовать общую версию из std, поскольку шаблонный своп является точным совпадением (и он избегает проблемы только замены базовых частей ваши производные объекты).

ПРИМЕЧАНИЕ. Я обновил это, чтобы удалить неправильные биты из моего последнего ответа. D'о! (спасибо puetzk и j_random_hacker за указание)

Ответ 4

Хотя верно, что обычно не следует добавлять материал в пространство имен std::, специально разрешено добавлять специализированные шаблоны для пользовательских типов. Перегрузка функций - нет. Это тонкая разница: -)

17.4.3.1/1   Это undefined для программы на С++ для добавления объявлений или определений   в пространство имен std или пространства имен с пространством имен std, если иначе   указано. Программа может добавлять специализированные шаблоны для любых   стандартный шаблон библиотеки в пространство имен std. Такая специализация   (полная или частичная) стандартной библиотеки приводит к undefined  если декларация зависит от пользовательского имени   внешняя связь, и если специализация шаблона не соответствует   стандартные требования к библиотеке для исходного шаблона.

Специализация std:: swap будет выглядеть так:

namespace std
{
    template<>
    void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}

Без бита template < > это будет перегрузка, которая является undefined, а не специализацией, которая разрешена. @Wilka предлагает подход к изменению пространства имен по умолчанию может работать с кодом пользователя (из-за поиска Koenig, предпочитающего версию без пространства имен), но она не гарантируется и на самом деле на самом деле не предполагается (реализация STL должна полностью использовать -qualified std:: swap).

Существует поток на comp.lang.С++. модерируется с длинным описанием темы. Однако большинство из них касается частичной специализации (что в настоящее время нет хорошего способа сделать).