Überladung des Operators: Elementfunktion vs. Nichtmitgliedsfunktion?

121

Ich habe gelesen, dass ein überladener Operator, der als Elementfunktion deklariert ist, asymmetrisch ist, da er nur einen Parameter haben kann und der andere Parameter, der automatisch übergeben wird, der thisZeiger ist. Es gibt also keinen Standard, um sie zu vergleichen. Auf der anderen Seite, überladener Operator deklarierte als friendist symmetrisch , weil wir zwei Argumente des gleichen Typs übergeben , und daher können sie verglichen werden.

Meine Frage ist, warum Freunde bevorzugt werden, wenn ich den Wert eines Zeigers immer noch mit einer Referenz vergleichen kann? (Die Verwendung einer asymmetrischen Version führt zu denselben Ergebnissen wie die symmetrische Version.) Warum verwenden STL-Algorithmen nur symmetrische Versionen?

Badmaash
quelle
11
Ihre Frage betrifft eigentlich nur binäre Operatoren. Nicht alle überladenen Operatoren sind auf einen einzigen Parameter beschränkt. Der Operator () kann eine beliebige Anzahl von Parametern annehmen. Unäre Operatoren können dagegen keine Parameter haben.
Charles Salvia
4
Dies ist eines von vielen Themen, die in den C ++ - FAQ behandelt werden: Überladen von Operatoren
Ben Voigt

Antworten:

148

Wenn Sie Ihre vom Operator überladene Funktion als Elementfunktion definieren, übersetzt der Compiler Ausdrücke wie s1 + s2in s1.operator+(s2). Das heißt, die vom Operator überladene Elementfunktion wird beim ersten Operanden aufgerufen. So funktionieren Mitgliedsfunktionen!

Aber was ist, wenn der erste Operand keine Klasse ist? Es gibt ein großes Problem, wenn wir einen Operator überladen möchten, bei dem der erste Operand kein Klassentyp ist double. Sie können also nicht so schreiben 10.0 + s2. Sie können jedoch eine vom Operator überladene Elementfunktion für Ausdrücke wie schreiben s1 + 10.0.

Um dieses Ordnungsproblem zu lösen, definieren wir eine überladene Funktion des Operators als friendWENN sie auf privateMitglieder zugreifen muss . Machen Sie es friendNUR, wenn es auf private Mitglieder zugreifen muss. Andernfalls machen Sie es einfach zu einer Nicht-Freund-Nicht-Mitglied- Funktion, um die Kapselung zu verbessern !

class Sample
{
 public:
    Sample operator + (const Sample& op2); //works with s1 + s2
    Sample operator + (double op2); //works with s1 + 10.0

   //Make it `friend` only when it needs to access private members. 
   //Otherwise simply make it **non-friend non-member** function.
    friend Sample operator + (double op1, const Sample& op2); //works with 10.0 + s2
}

Lesen Sie diese:
Ein kleines Problem bei der Reihenfolge in Operanden
Wie Nicht-Mitglieder-Funktionen die Kapselung verbessern

Nawaz
quelle
2
"Machen Sie es friendnur, wenn es auf private Mitglieder zugreifen muss ... und wenn Sie nicht gelangweilt sind / sind, Accessoren zu schreiben, richtig?
Badmaash
4
@Abhi: Wählen Sie Ihre Wahl: Verbesserte Verkapselung gegen faule Schreibgewohnheiten!
Nawaz
6
@matthias, nicht alle Operatoren sind kommutativ. Ein einfaches Beispiel ist a/b.
edA-qa mort-ora-y
3
Ein üblicher Weg, um zu vermeiden, dass Ihre Nichtmitgliedsbetreiber Anforderungen stellen müssen, friendbesteht darin, sie in Bezug auf die Operatoren für die Zuweisung von Operationen (die mit ziemlicher Sicherheit öffentliche Mitglieder sein werden) zu implementieren. Sie können beispielsweise T T::operator+=(const T &rhs)als Mitglied definieren und dann Nichtmitglied T operator(T lhs, const T &rhs)als definieren return lhs += rhs;. Die Nichtmitgliedsfunktion sollte im selben Namespace wie die Klasse definiert werden.
Adrian McCarthy
2
@ricky: Aber wenn das lhs eine Kopie ist (wie in meinem Kommentar), dann spielt die Tatsache, dass sich das lhs ändert, keine Rolle.
Adrian McCarthy
20

Es ist nicht unbedingt eine Unterscheidung zwischen friendOperatorüberladungen und Elementfunktionsoperatorüberlastungen, sondern zwischen globalen Operatorüberladungen und Elementfunktionsoperatorüberlastungen.

Ein Grund, eine globale Operatorüberladung zu bevorzugen, besteht darin, Ausdrücke zuzulassen, bei denen der Klassentyp auf der rechten Seite eines binären Operators angezeigt wird . Beispielsweise:

Foo f = 100;
int x = 10;
cout << x + f;

Dies funktioniert nur, wenn eine globale Operatorüberlastung für vorliegt

Foo-Operator + (int x, const Foo & f);

Beachten Sie, dass die globale Operatorüberladung nicht unbedingt eine friendFunktion sein muss. Dies ist nur erforderlich, wenn Zugriff auf private Mitglieder von erforderlich Fooist, dies ist jedoch nicht immer der Fall.

Unabhängig davon, wenn Foonur ein Elementfunktionsoperator überlastet wäre, wie:

class Foo
{
  ...
  Foo operator + (int x);
  ...
};

... dann könnten wir nur Ausdrücke haben, bei denen eine FooInstanz links vom Plus-Operator erscheint.

Charles Salvia
quelle
3
+1 für die Unterscheidung zwischen Mitgliedsfunktionen und Nicht-Mitgliederfunktionen anstelle von Mitglieder- und Freundfunktionen. Ich denke, heute würden wir "globaler oder Namespace-Bereich" sagen.
Adrian McCarthy