Esistono diversi modi per scrivere swap
, alcuni meglio di altri. Nel tempo, tuttavia, è stato scoperto che una singola definizione funziona meglio. Consideriamo come potremmo pensare di scrivere una swap
funzione.
Innanzitutto vediamo che contenitori come std::vector<>
hanno una funzione membro a argomento singolo swap
, come ad esempio:
struct vector
{
void swap(vector&) { /* swap members */ }
};
Naturalmente, quindi, anche la nostra classe dovrebbe, giusto? Beh, non proprio. La libreria standard ha ogni sorta di cose inutili e un membro swap
è una di queste. Perché? Andiamo avanti
Ciò che dovremmo fare è identificare ciò che è canonico e ciò che la nostra classe deve fare per lavorare con esso. E il metodo canonico di scambio è con std::swap
. Questo è il motivo per cui le funzioni membro non sono utili: non sono il modo in cui dovremmo scambiare le cose, in generale, e non influire sul comportamento di std::swap
.
Bene, quindi, per rendere il std::swap
lavoro dovremmo fornire (e avremmo dovuto fornire std::vector<>
) una specializzazione std::swap
, giusto?
namespace std
{
template <> // important! specialization in std is OK, overloading is UB
void swap(myclass&, myclass&)
{
// swap
}
}
Bene, sicuramente funzionerebbe in questo caso, ma ha un evidente problema: le specializzazioni di funzioni non possono essere parziali. Cioè, non possiamo specializzare le classi di template con questo, solo particolari istanze:
namespace std
{
template <typename T>
void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
{
// swap
}
}
Questo metodo funziona qualche volta, ma non sempre. Deve esserci un modo migliore.
C'è! Possiamo usare una friend
funzione e trovarla tramite ADL :
namespace xyz
{
struct myclass
{
friend void swap(myclass&, myclass&);
};
}
Quando vogliamo scambiare qualcosa, associamo † std::swap
e quindi facciamo una chiamata non qualificata:
using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first
// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap
Cos'è una friend
funzione? C'è confusione in quest'area.
Prima che il C ++ fosse standardizzato, le friend
funzioni facevano qualcosa chiamato "iniezione del nome amico", in cui il codice si comportava come se la funzione fosse stata scritta nello spazio dei nomi circostante. Ad esempio, questi erano pre-standard equivalenti:
struct foo
{
friend void bar()
{
// baz
}
};
// turned into, pre-standard:
struct foo
{
friend void bar();
};
void bar()
{
// baz
}
Tuttavia, quando è stato inventato ADL questo è stato rimosso. La friend
funzione può quindi essere trovata solo tramite ADL; se lo volevi come funzione gratuita, doveva essere dichiarato come tale ( vedi questo , per esempio). Ma ecco! C'era un problema.
Se lo usi std::swap(x, y)
, il tuo sovraccarico non sarà mai trovato, perché hai esplicitamente detto "guarda dentro std
e nessun altro"! Questo è il motivo per cui alcune persone hanno suggerito di scrivere due funzioni: una come funzione che si trova tramite ADL e l'altra per gestire std::
qualifiche esplicite .
Ma come abbiamo visto, questo non può funzionare in tutti i casi e finiamo con un brutto pasticcio. Invece, lo scambio idiomatico è andato dall'altra parte: invece di renderlo il compito delle classi da fornire std::swap
, è compito degli scambiatori assicurarsi che non usino qualificati swap
, come sopra. E questo tende a funzionare abbastanza bene, purché le persone lo sappiano. Ma qui sta il problema: non è intuitivo dover usare una chiamata non qualificata!
Per rendere ciò più semplice, alcune librerie come Boost hanno fornito la funzione boost::swap
, che esegue semplicemente una chiamata non qualificata swap
, con std::swap
uno spazio dei nomi associato. Questo aiuta a rendere le cose di nuovo succinte, ma è ancora un peccato.
Si noti che non vi è alcun cambiamento nel comportamento di C ++ 11 std::swap
, cosa che io e altri abbiamo erroneamente pensato fosse il caso. Se sei stato morso da questo, leggi qui .
In breve: la funzione membro è solo rumore, la specializzazione è brutta e incompleta, ma la friend
funzione è completa e funziona. E quando si scambia, utilizzare boost::swap
o un non qualificato swap
con std::swap
associati.
† Informalmente, viene associato un nome se verrà preso in considerazione durante una chiamata di funzione. Per i dettagli, leggere §3.4.2. In questo caso, std::swap
normalmente non viene considerato; ma possiamo associarlo (aggiungerlo all'insieme di sovraccarichi considerati da non qualificati swap
), consentendone la ricerca.