Domanda

std::swap() è utilizzato da molti contenitori std (come std::list E std::vector) durante l'ordinamento e anche l'assegnazione.

Ma l'implementazione standard di swap() è molto generalizzato e piuttosto inefficiente per i tipi personalizzati.

Pertanto l'efficienza può essere ottenuta sovraccaricando std::swap() con un'implementazione specifica del tipo personalizzato.Ma come puoi implementarlo in modo che venga utilizzato dai contenitori std?

È stato utile?

Soluzione

Il modo giusto per sovraccaricare lo swap è scriverlo nello stesso spazio dei nomi di ciò che stai scambiando, in modo che possa essere trovato tramite ricerca dipendente dall'argomento (ADL).Una cosa particolarmente semplice da fare è:

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

Altri suggerimenti

Attenzione Mozza314

Ecco una simulazione degli effetti di un generico std::algorithm chiamando std::swap, e chiedere all'utente di fornire lo scambio nello spazio dei nomi std.Poiché si tratta di un esperimento, questa simulazione utilizza namespace exp invece di 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);
}

Per me questo viene stampato:

generic exp::swap

Se il tuo compilatore stampa qualcosa di diverso, non sta implementando correttamente la "ricerca a due fasi" per i modelli.

Se il tuo compilatore è conforme (a uno qualsiasi dei C++98/03/11), fornirà lo stesso output che mostro.E in quel caso accade esattamente ciò che temi che accada.E mettendo il tuo swap nello spazio dei nomi std (exp) non ha impedito che ciò accadesse.

Dave ed io siamo entrambi membri del comitato e lavoriamo su quest'area dello standard da un decennio (e non sempre in accordo tra loro).Ma la questione è stata risolta da molto tempo e siamo entrambi d'accordo su come è stata risolta.Ignora l'opinione / risposta dell'esperto di Dave in quest'area a tuo rischio e pericolo.

Questo problema è venuto alla luce dopo la pubblicazione di C++98.A partire dal 2001, io e Dave abbiamo iniziato a farlo lavorare in quest'area.E questa è la soluzione moderna:

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

L'output è:

swap(A, A)

Aggiornamento

È stata fatta un'osservazione che:

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

}

lavori!Allora perché non usarlo?

Considera il caso in cui il tuo A è un modello di classe:

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

Adesso non funziona più.:-(

Quindi potresti mettere swap nello spazio dei nomi std e farlo funzionare.Ma dovrai ricordarti di mettere swap In Anel caso in cui hai un modello: A<T>.E poiché entrambi i casi funzioneranno se metti swap In Aè semplicemente più facile da ricordare (e insegnare agli altri) a farlo in un certo modo.

Non è consentito (secondo lo standard C++) sovraccaricare std::swap, tuttavia è consentito specificamente aggiungere specializzazioni di modello per i propri tipi allo spazio dei nomi std.Per esempio.

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

quindi gli utilizzi nei contenitori std (e altrove) sceglieranno la tua specializzazione anziché quella generale.

Tieni inoltre presente che fornire un'implementazione della classe base di swap non è sufficiente per i tipi derivati.Per esempio.se hai

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

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

funzionerà per le classi Base, ma se provi a scambiare due oggetti derivati ​​utilizzerà la versione generica da std perché lo scambio basato su modello è una corrispondenza esatta (ed evita il problema di scambiare solo le parti 'base' dei tuoi oggetti derivati ).

NOTA:L'ho aggiornato per rimuovere le parti errate dalla mia ultima risposta.D'oh!(grazie puetzk e j_random_hacker per averlo sottolineato)

Anche se è corretto che in genere non si debba aggiungere materiale a std::namespace, è specificamente consentita l'aggiunta di specializzazioni di modello per i tipi definiti dall'utente.Sovraccaricare le funzioni non lo è.Questa è una sottile differenza :-)

17.4.3.1/1 Non è definito per un programma C ++ aggiungere dichiarazioni o definizioni allo spazio dei nomi o spazi dei nomi con SPST dello spazio dei nomi se non diversamente specificato.Un programma può aggiungere specializzazioni di modelli per qualsiasi modello di libreria standard allo spazio dei nomi.Tale specializzazione (completa o parziale) di una libreria standard comporta un comportamento non definito a meno che la dichiarazione non dipenda da un nome di collegamento esterno definito dall'utente e, a meno che la specializzazione del modello non soddisfi i requisiti della libreria standard per il modello originale.

Una specializzazione di std::swap sarebbe simile a:

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

Senza il template<> bit si tratterebbe di un sovraccarico, che non è definito, piuttosto che di una specializzazione, che è consentita.L'approccio suggerito da @Wilka di modificare lo spazio dei nomi predefinito potrebbe funzionare con il codice utente (a causa della ricerca di Koenig che preferisce la versione senza spazio dei nomi) ma non è garantito, e in effetti non è proprio previsto (l'implementazione STL dovrebbe utilizzare la versione completa -qualificato std::swap).

C'è un discussione su comp.lang.c++.moderated con un lungo discussione dell'argomento.La maggior parte, però, riguarda una specializzazione parziale (che al momento non esiste un buon modo per farlo).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top