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

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

Но стандартная реализация 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Пространство имен для случая, когда у вас есть шаблон: A<T>, И так как оба случая будут работать, если вы положите swap в AПространство имен, проще запомнить (и научить других) просто сделать это одним способом.

Вы не можете (по стандарту 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, потому что шаблонный своп является точным совпадением (и это позволяет избежать проблемы только замены "базовых" частей ваших производных объектов).

ПРИМЕЧАНИЕ: я обновил это, чтобы удалить неправильные биты из моего последнего ответа. D'о! (спасибо 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 подход к изменению пространства имен по умолчанию может работать с пользовательским кодом (из-за того, что поиск Кенига предпочитает версию без пространства имен), но это не гарантировано, и на самом деле не предполагается (реализация STL должна использовать полностью квалифицированный std::swap).

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

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