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

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

109 голосов
/ 14 августа 2008

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

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

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

Ответы [ 4 ]

123 голосов
/ 21 апреля 2010

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

Внимание Моцца314

Вот симуляция эффектов универсального 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, просто будет легче запомнить (и научить других) просто сделать это одним способом.

52 голосов
/ 14 августа 2008

Вы не можете (по стандарту 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 за указание на это)

29 голосов
/ 21 сентября 2008

Хотя правильно, что вообще не следует добавлять вещи в пространство имен 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 ++. Есть ветка , модерируемая с long обсуждение темы. Тем не менее, в большей части речь идет о частичной специализации (чего в настоящее время не существует).

...