Es gibt verschiedene Möglichkeiten zu schreiben swap
, einige besser als andere. Im Laufe der Zeit wurde jedoch festgestellt, dass eine einzige Definition am besten funktioniert. Lassen Sie uns überlegen, wie wir über das Schreiben einer swap
Funktion nachdenken könnten .
Wir sehen zuerst, dass Container wie std::vector<>
eine Elementfunktion mit einem Argument haben swap
, wie zum Beispiel:
struct vector
{
void swap(vector&) { /* swap members */ }
};
Natürlich sollte unsere Klasse das auch, oder? Nicht wirklich. Die Standardbibliothek enthält alle möglichen unnötigen Dinge , und ein Mitglied swap
ist eines davon. Warum? Lass uns weiter gehen.
Was wir tun sollten, ist herauszufinden, was kanonisch ist und was unsere Klasse tun muss , um damit zu arbeiten. Und die kanonische Methode des Austauschs ist mit std::swap
. Aus diesem Grund sind Mitgliedsfunktionen nicht nützlich: Sie sind nicht die Art und Weise, wie wir Dinge im Allgemeinen austauschen sollten, und haben keinen Einfluss auf das Verhalten von std::swap
.
Nun, um std::swap
Arbeit zu machen , sollten wir std::vector<>
eine Spezialisierung von bereitstellen (und hätten bereitstellen sollen) std::swap
, richtig?
namespace std
{
template <> // important! specialization in std is OK, overloading is UB
void swap(myclass&, myclass&)
{
// swap
}
}
Nun, das würde in diesem Fall sicherlich funktionieren, aber es hat ein eklatantes Problem: Funktionsspezialisierungen können nicht partiell sein. Das heißt, wir können keine Vorlagenklassen damit spezialisieren, sondern nur bestimmte Instanziierungen:
namespace std
{
template <typename T>
void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
{
// swap
}
}
Diese Methode funktioniert manchmal, aber nicht immer. Es muss einen besseren Weg geben.
Es gibt! Wir können eine friend
Funktion verwenden und sie über ADL finden :
namespace xyz
{
struct myclass
{
friend void swap(myclass&, myclass&);
};
}
Wenn wir etwas tauschen möchten, assoziieren wir † std::swap
und tätigen dann einen unqualifizierten Anruf:
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
Was ist eine friend
Funktion? In diesem Bereich herrscht Verwirrung.
Bevor C ++ standardisiert wurde, haben friend
Funktionen eine sogenannte "Friend Name Injection" ausgeführt, bei der sich der Code so verhielt, als ob die Funktion in den umgebenden Namespace geschrieben worden wäre. Zum Beispiel waren dies äquivalente Vorstandards:
struct foo
{
friend void bar()
{
// baz
}
};
// turned into, pre-standard:
struct foo
{
friend void bar();
};
void bar()
{
// baz
}
Als ADL erfunden wurde, wurde dies jedoch entfernt. Die friend
Funktion konnte dann nur über ADL gefunden werden; Wenn Sie es als freie Funktion haben wollten, musste es als solches deklariert werden ( siehe dies zum Beispiel). Aber siehe da! Es gab ein Problem.
Wenn Sie nur verwenden std::swap(x, y)
, wird Ihre Überlastung nie gefunden, weil Sie ausdrücklich gesagt haben "schauen Sie rein std
und nirgendwo anders"! Aus diesem Grund schlugen einige Leute vor, zwei Funktionen zu schreiben: eine als Funktion, die über ADL gefunden werden kann , und die andere, um explizite std::
Qualifikationen zu handhaben .
Aber wie wir gesehen haben, kann dies nicht in allen Fällen funktionieren und wir haben ein hässliches Durcheinander. Stattdessen ging das idiomatische Tauschen den anderen Weg: Anstatt es zum Job der Klassen std::swap
zu machen, ist es die Aufgabe der Swapper, sicherzustellen, dass sie keine qualifizierten Mitarbeiter swap
wie oben verwenden. Und das funktioniert normalerweise ziemlich gut, solange die Leute davon wissen. Aber darin liegt das Problem: Es ist nicht intuitiv, einen unqualifizierten Anruf verwenden zu müssen!
Um dies zu vereinfachen, haben einige Bibliotheken wie Boost die Funktion boost::swap
, die nur einen unqualifizierten Aufruf ausführt swap
, std::swap
als zugehörigen Namespace bereitgestellt . Dies hilft, die Dinge wieder kurz zu machen, aber es ist immer noch ein Mist.
Beachten Sie, dass sich in C ++ 11 das Verhalten von nicht ändert std::swap
, was ich und andere fälschlicherweise für möglich gehalten haben. Wenn Sie davon gebissen wurden, lesen Sie hier .
Kurz gesagt: Die Elementfunktion ist nur Rauschen, die Spezialisierung ist hässlich und unvollständig, aber die friend
Funktion ist vollständig und funktioniert. Und wenn Sie tauschen, verwenden Sie entweder boost::swap
oder nicht qualifiziert swap
mit std::swap
verbunden.
† Informell ist ein Name zugeordnet, wenn er bei einem Funktionsaufruf berücksichtigt wird. Einzelheiten finden Sie in §3.4.2. In diesem Fall wird std::swap
normalerweise nicht berücksichtigt; aber wir können assoziieren es (fügen Sie den Satz von Überlastungen durch unqualifizierte betrachtet swap
), so dass es gefunden werden.