In der schönen Antwort auf die Copy-and-Swap-Sprache gibt es einen Code, für den ich ein bisschen Hilfe brauche:
class dumb_array
{
public:
// ...
friend void swap(dumb_array& first, dumb_array& second) // nothrow
{
using std::swap;
swap(first.mSize, second.mSize);
swap(first.mArray, second.mArray);
}
// ...
};
und er fügt eine Notiz hinzu
Es gibt andere Behauptungen, dass wir std :: swap für unseren Typ spezialisieren, einen Swap in der Klasse neben einem Swap mit freien Funktionen bereitstellen sollten usw. Dies ist jedoch alles unnötig: Jede ordnungsgemäße Verwendung des Swaps erfolgt durch einen nicht qualifizierten Aufruf und unsere Funktion wird über ADL gefunden. Eine Funktion reicht aus.
Da friend
ich ein bisschen "unfreundlich" bin, muss ich zugeben. Meine Hauptfragen sind also:
- sieht aus wie eine freie Funktion , aber es ist innerhalb des Klassenkörpers?
- Warum ist das nicht
swap
statisch ? Es werden offensichtlich keine Mitgliedsvariablen verwendet. - "Jede ordnungsgemäße Verwendung von Swap wird Swap über ADL herausfinden" ? ADL durchsucht die Namespaces, oder? Aber schaut es auch in Klassen? Oder kommt hier was
friend
rein?
Nebenfragen:
- Soll ich mit C ++ 11 mein
swap
s mit markierennoexcept
? - Mit C ++ 11 und dessen Bereichs für , soll ich setzen
friend iter begin()
und auffriend iter end()
die gleiche Weise in der Klasse? Ich denke dasfriend
wird hier nicht gebraucht, oder?
c++
c++11
friend
copy-and-swap
Towi
quelle
quelle
friend
Funktion überhaupt keine Mitgliedsfunktion ist?Antworten:
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 einerswap
Funktion nachdenken könnten .Wir sehen zuerst, dass Container wie
std::vector<>
eine Elementfunktion mit einem Argument habenswap
, wie zum Beispiel: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 vonstd::swap
.Nun, um
std::swap
Arbeit zu machen , sollten wirstd::vector<>
eine Spezialisierung von bereitstellen (und hätten bereitstellen sollen)std::swap
, richtig?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:
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 :Wenn wir etwas tauschen möchten, assoziieren wir †
std::swap
und tätigen dann einen unqualifizierten Anruf: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: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 reinstd
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 explizitestd::
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 Mitarbeiterswap
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ührtswap
,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 entwederboost::swap
oder nicht qualifiziertswap
mitstd::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 betrachtetswap
), so dass es gefunden werden.quelle
std::vector<std::string>().swap(someVecWithData);
, was mit einerswap
freien Funktion nicht möglich ist, da beide Argumente als nicht konstante Referenz übergeben werden.operator=
,operator+
und befürwortenoperator+=
, aber es ist klar, dass diese Operatoren für relevante Klassen für Symmetrie akzeptiert / erwartet werden. Gleiches gilt meiner Meinung nach für Mitgliederswap
+ Namespace-Bereicheswap
.function<void(A*)> f; if(!f) { }
kann scheitern, nur weil es einA
deklariertoperator!
, dasf
genauso gut akzeptiert wief
das eigeneoperator!
(unwahrscheinlich, kann aber passieren). Wennfunction<>
der Autor dachte "Oh, ich habe einen 'Operator Bool', warum sollte ich 'Operator!' Implementieren? Das würde gegen DRY verstoßen!", Wäre das fatal. Sie müssen nur einoperator!
fürA
undA
einen Konstruktor für ein implementierenfunction<...>
, und die Dinge werden kaputt gehen, da beide Kandidaten benutzerdefinierte Konvertierungen erfordern.Dieser Code entspricht (in fast jeder Hinsicht):
Eine innerhalb einer Klasse definierte Freundfunktion ist:
inline
Die genauen Regeln finden Sie im Abschnitt
[class.friend]
(ich zitiere die Absätze 6 und 7 des C ++ 0x-Entwurfs):quelle
swap
nur für ADL sichtbar ist. Es ist ein Mitglied des umschließenden Namespace, aber sein Name ist für die anderen Namenssuchformulare nicht sichtbar. EDIT: Ich sehe, dass @GMan wieder schneller war :) @Ben es war immer so in der ISO C ++ :)friend
Funktionen werden nur von ADL gefunden, und wenn es sich nur um freie Funktionen mitfriend
Zugriff handeln muss, müssen sie sowohl alsfriend
innerhalb der Klasse als auch als normale Deklaration für freie Funktionen außerhalb der Klasse deklariert werden . Sie können diese Notwendigkeit zum Beispiel in dieser Antwort sehen .