Вопрос

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

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

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

Это было полезно?

Решение

Правильный способ перегрузить 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);
        // ...
    }
};

Другие советы

Внимание , Mozza314

Вот моделирование эффектов универсального std::algorithm зовущий std::swap, и заставить пользователя предоставить свой 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 (и где бы то ни было еще) выберут вашу специализацию вместо общей.

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top