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

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

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

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

Ответов (4)

Решение

Правильный способ перегрузки свопа - записать его в том же пространстве имен, что и то, что вы обмениваете, чтобы его можно было найти с помощью поиска, зависящего от аргументов (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);
        // ...
    }
};

Внимание 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

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

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

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

Эта проблема обнаружилась после публикации C++ 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 пространство имен «S для случая , когда у вас есть шаблон: A<T> . А так как в обоих случаях будет работать , если вы положили swap в пространстве A имен «s, это просто легче запомнить (и учить других) , чтобы просто сделать это , что так.

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

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

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

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

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

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

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

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

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

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

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

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

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

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