Jak przeciążać std:: swap()

std::swap() jest używany przez wiele kontenerów std (takich jak std::list i std::vector) podczas sortowania, a nawet przypisywania.

Ale implementacja std swap() jest bardzo uogólniona i raczej nieefektywna dla typów niestandardowych.

W ten sposób wydajność może być uzyskana przez przeciążenie std::swap() z niestandardową implementacją specyficzną dla typu. Ale jak można go zaimplementować, aby był używany przez kontenery std?

Author: sbi, 2008-08-14

4 answers

Właściwym sposobem na przeciążenie swapu jest zapisanie go w tej samej przestrzeni nazw co Zamiana, tak aby można go było znaleźć za pomocą zależnego od argumentów wyszukiwania (ADL). Jedną szczególnie łatwą rzeczą jest:

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);
        // ...
    }
};
 114
Author: Dave Abrahams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-21 16:02:22

Attention Mozza314

Oto symulacja efektów ogólnego std::algorithm wywołania std::swap, a użytkownik dostarcza swap w przestrzeni nazw std. Ponieważ jest to eksperyment, ta symulacja używa namespace exp zamiast 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);
}

Dla mnie to drukuje:

generic exp::swap

Jeśli twój kompilator wypisuje coś innego, to nie implementuje poprawnie "dwufazowego wyszukiwania" szablonów.

Jeśli twój kompilator jest zgodny (z którymkolwiek z C++98/03/11), wtedy da to samo wyjście, które pokazałem. I w takim przypadku dokładnie to, czego się obawiasz, stanie się. I umieszczenie swap w przestrzeni nazw std (exp) nie powstrzymało to przed tym.

Dave i ja jesteśmy członkami komisji i pracujemy w tym obszarze standardu od dekady (i nie zawsze w zgodzie ze sobą). Ale ta kwestia została rozwiązana przez długi czas i oboje zgadzamy się co do tego, jak została rozwiązana. Lekceważ eksperta Dave ' a opinia / odpowiedź w tej dziedzinie na własne ryzyko.

Ten numer wyszedł na jaw po opublikowaniu C++98. Od około 2001 roku Dave i ja zaczęliśmy pracować w tym obszarze . I to jest nowoczesne rozwiązanie:

// 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);
}

Wyjście to:

swap(A, A)

Update

Zauważono, że:

namespace exp
{    
    template <>
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}
Działa! Więc dlaczego tego nie użyć?

Rozważ przypadek, że Twój A jest szablonem klasy:

// 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);
}
Teraz to już nie działa. :-(

Więc możesz umieścić swap w przestrzeni nazw std i mieć to działa. Ale musisz pamiętać, aby umieścić swap w przestrzeni nazw A W przypadku, gdy masz szablon: A<T>. A ponieważ oba przypadki zadziałają, jeśli umieścisz swap w przestrzeni nazw A, po prostu łatwiej jest zapamiętać (i nauczyć innych) robić to w ten sposób.

 62
Author: Howard Hinnant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-31 17:13:17

Nie możesz (przez standard C++) przeciążać std:: swap, jednak możesz dodawać specjalizacje szablonów dla własnych typów do przestrzeni nazw std. Np.

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

Wtedy zwyczaje w kontenerach std (i gdziekolwiek indziej) wybiorą Twoją specjalizację zamiast ogólnej.

Zauważ również, że dostarczenie implementacji klasy bazowej swapu nie jest wystarczająco dobre dla Twoich typów pochodnych. Np. jeśli masz

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

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

To zadziała dla bazy klasy, ale jeśli spróbujesz zamienić dwa pochodne obiekty, użyje on wersji generycznej ze std, ponieważ template swap jest dokładnie dopasowany (i pozwala uniknąć problemu zamiany tylko "podstawowych" części obiektów pochodnych).

Uwaga: zaktualizowałem to, aby usunąć złe bity z mojej ostatniej odpowiedzi. D ' Oh! (dzięki puetzk i j_random_hacker za wskazanie tego)

 50
Author: Wilka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-27 14:15:05

Chociaż jest poprawne, że nie powinno się dodawać rzeczy do przestrzeni nazw std::, dodawanie specjalizacji szablonów dla typów zdefiniowanych przez użytkownika jest szczególnie dozwolone. Przeciążenie funkcji nie jest. To subtelna różnica: -)

17.4.3.1/1 Dla programu C++ nie jest zdefiniowane dodawanie deklaracji lub definicji do przestrzeni nazw std lub przestrzeni nazw z przestrzenią nazw std, chyba że określone. Program może dodawać szablony specjalizacji dla dowolnych biblioteka standardowa szablon do przestrzeni nazw std. Taka specjalizacja (całkowita lub częściowa) biblioteka standardowa daje wynik nieokreślony zachowanie, chyba że deklaracja zależy od zdefiniowanej przez użytkownika nazwy linkowania zewnętrznego i chyba że specjalizacja szablonu spełnia standardowe wymagania biblioteczne dla oryginalnego szablonu.

Specjalizacja std:: swap wyglądałaby następująco:

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

Bez szablonu bit byłby przeciążeniem, które jest niezdefiniowane, a nie specjalizacja, która jest dozwolona. Sugerowane przez @ Wilka podejście do zmiany domyślnej przestrzeni nazw może działać z kodem użytkownika (ze względu na to, że Koenig lookup preferuje wersję bez przestrzeni nazw), ale nie jest gwarantowane i tak naprawdę nie powinno (implementacja STL powinna używać w pełni kwalifikowanej STD:: swap).

Istnieje wątek na komp.lang.c++.moderowany z długi temat. Większość z nich dotyczy jednak częściowej specjalizacji (która jest obecnie nie jest to dobry sposób).

 29
Author: puetzk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-20 22:37:47