Сведения о вопросе

rjevskii

21:43, 23rd August, 2020

Теги

c++   performance   optimization   stl   c++-faq    

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

Просмотров: 523   Ответов: 4

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

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

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



  Сведения об ответе

screen

06:04, 24th August, 2020

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


  Сведения об ответе

P_S_S

11:06, 17th August, 2020

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

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

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


  Сведения об ответе

PIRLO

03:22, 15th August, 2020

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

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

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

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

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

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

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

NOTE: я обновил это, чтобы удалить неправильные биты из моего последнего ответа. Д'о! (спасибо puetzk и j_random_hacker за указание на это)


  Сведения об ответе

lesha

10:02, 24th August, 2020

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

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

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

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

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

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться