Überladen von Mitgliedszugriffsoperatoren -> ,. *

129

Ich die meisten Betreiber Überlastung verstehen, mit Ausnahme der Mitgliedszugangsbetreiber ->, .*,->* usw.

Was wird insbesondere an diese Operatorfunktionen übergeben und was sollte zurückgegeben werden?

Woher weiß die Bedienerfunktion (z. B. operator->(...)), auf welches Mitglied verwiesen wird? Kann es wissen? Muss es überhaupt wissen?

Gibt es schließlich irgendwelche konstanten Überlegungen, die berücksichtigt werden müssen? Wenn Sie beispielsweise so etwas überladen operator[], benötigen Sie im Allgemeinen sowohl eine konstante als auch eine nicht konstante Version. Benötigen Mitgliederzugriffsoperatoren konstante und nicht konstante Versionen?

Bingo
quelle
1
Ich glaube, die obige C ++ - FAQ berührt alle Fragen, die in der obigen Frage gestellt wurden.
Alok Save
constund Nichtversionen constvon operator->sind nicht erforderlich , aber es kann nützlich sein, beide bereitzustellen.
Fred Foo
1
Siehe auch: yosefk.com/c++fqa/operator.html
György Andrasek
9
@Als: In den FAQ wird nicht erklärt, wie ->*und .*. Tatsächlich werden sie nicht einmal erwähnt! Ich denke, sie sind zu selten, um in einer FAQ zu sein, aber ich würde diese Frage gerne aus der FAQ verlinken. Bitte schließen Sie dies nicht als Betrug der FAQ!
sbi
@sbi, ich habe in Ihren (großartigen) FAQ keinen Link zu dieser Frage gefunden und am Ende eine doppelte Frage gestellt. Könnten Sie es offensichtlicher machen? (Entschuldigung, wenn es schon offensichtlich ist).
P i

Antworten:

144

->

Dies ist die einzige wirklich knifflige. Es muss eine nicht statische Elementfunktion sein und es werden keine Argumente akzeptiert. Der Rückgabewert wird verwendet, um die Mitgliedersuche durchzuführen.

Wenn der Rückgabewert ein anderes Objekt vom Klassentyp und kein Zeiger ist, wird die nachfolgende Mitgliedssuche auch von einer operator->Funktion verarbeitet. Dies wird als "Drilldown-Verhalten" bezeichnet. Die Sprache verkettet die operator->Aufrufe, bis der letzte einen Zeiger zurückgibt.

struct client
    { int a; };

struct proxy {
    client *target;
    client *operator->() const
        { return target; }
};

struct proxy2 {
    proxy *target;
    proxy &operator->() const
        { return * target; }
};

void f() {
    client x = { 3 };
    proxy y = { & x };
    proxy2 z = { & y };

    std::cout << x.a << y->a << z->a; // print "333"
}

->*

Dieser ist nur insofern schwierig, als nichts Besonderes daran ist. Die nicht überladene Version erfordert ein Objekt des Zeigers auf den Klassentyp auf der linken Seite und ein Objekt des Zeigers auf den Elementtyp auf der rechten Seite. Wenn Sie es jedoch überladen, können Sie beliebige Argumente verwenden und alles zurückgeben, was Sie möchten. Es muss nicht einmal ein nicht statisches Mitglied sein.

Mit anderen Worten, dies ist ein nur ein normaler Binäroperators wie +, -und /. Siehe auch: Sind freie Operatoren -> * Überladungen böse?

.* und .

Diese können nicht überladen werden. Es gibt bereits eine eingebaute Bedeutung, wenn die linke Seite vom Klassentyp ist. Vielleicht wäre es ein wenig sinnvoll, sie für einen Zeiger auf der linken Seite definieren zu können, aber das Komitee für Sprachdesign entschied, dass dies eher verwirrend als nützlich wäre.

Überlastung ->, ->*, ., und .*in Fällen , nur füllen kann , wo ein Ausdruck undefiniert sein würde, kann es nie die Bedeutung eines Ausdrucks ändern, ohne eine Überlastung gültig wäre.

Kartoffelklatsche
quelle
2
Ihre letzte Aussage ist nicht ganz richtig. Beispielsweise können Sie den newOperator überladen , obwohl er auch dann gültig ist, wenn er nicht überladen ist.
Matt
6
@Matt gut, newist immer überladen oder Überladungsregeln gelten nicht wirklich für ihn (13.5 / 5: Die Zuordnungs- und Freigabefunktionen Operator neu, Operator neu [], Operator löschen und Operator löschen [] werden in 3.7 vollständig beschrieben 0,4. die Attribute und in dem Rest dieses Subklausel gefunden Einschränkungen gelten nicht für sie , sofern nicht ausdrücklich in 3.7.4 angegeben.) Aber einstellige Überlastung &oder binär &&, ||oder ,, oder das Hinzufügen von Überlastungen operator=oder Überlastung einfach alles für ein unscoped Aufzählungstyp, kann die Bedeutung eines Ausdrucks ändern. Klarstellung der Aussage, danke!
Potatoswatter
41

Operator -> ist etwas Besonderes.

"Es gibt zusätzliche atypische Einschränkungen: Es muss ein Objekt (oder einen Verweis auf ein Objekt) zurückgeben, das auch einen Zeiger-Dereferenzierungsoperator hat, oder es muss einen Zeiger zurückgeben, mit dem ausgewählt werden kann, auf was der Pfeil des Zeiger-Dereferenzierungsoperators zeigt. "" Bruce Eckel: Denken CPP Vol-One: Operator->

Die zusätzliche Funktionalität wird zur Vereinfachung bereitgestellt, sodass Sie nicht anrufen müssen

a->->func();

Sie können einfach tun:

a->func();

Das unterscheidet den Bediener -> von den anderen Bedienerüberlastungen.

Totonga
quelle
3
Diese Antwort verdient mehr Anerkennung. Sie können Eckels Buch über diesen Link herunterladen. Die Informationen finden Sie in Kapitel 12 von Band 1.
P i
26

Sie können den Mitgliederzugriff nicht überladen .(dh den zweiten Teil dessen, was ->funktioniert). Sie können jedoch die unäre Dereferenzierung überladen Operator *(dh der erste Teil von dem, was der ->Fall ist).

Der C ++ - ->Operator ist im Grunde die Vereinigung von zwei Schritten, und dies ist klar, wenn Sie denken, dass dies x->yäquivalent zu ist(*x).y . Mit C ++ können Sie anpassen, was mit dem (*x)Teil geschehen xsoll, wenn es sich um eine Instanz Ihrer Klasse handelt.

Die Semantik für das ->Überladen ist etwas seltsam, da Sie in C ++ entweder einen regulären Zeiger zurückgeben können (der zum Auffinden des spitzen Objekts verwendet wird) oder eine Instanz einer anderen Klasse zurückgeben können, wenn diese Klasse auch einen ->Operator bereitstellt . In diesem zweiten Fall wird die Suche nach dem dereferenzierten Objekt von dieser neuen Instanz aus fortgesetzt.

6502
quelle
2
Tolle Erklärung! Ich denke, das bedeutet dasselbe für ->*, da es der Form von (*x).*? Entspricht .
Bingo
10

Das -> Bediener weiß nicht, auf welches Mitglied verwiesen wird, sondern stellt lediglich ein Objekt bereit, für das der eigentliche Elementzugriff ausgeführt werden soll.

Außerdem sehe ich keinen Grund, warum Sie keine konstanten und nicht konstanten Versionen bereitstellen können.

John Chadwick
quelle
7

Wenn Sie den Operator -> () überladen (hier werden keine Argumente übergeben), ruft der Compiler tatsächlich -> rekursiv auf, bis er einen tatsächlichen Zeiger auf einen Typ zurückgibt. Es verwendet dann das richtige Mitglied / die richtige Methode.

Dies ist beispielsweise nützlich, um eine intelligente Zeigerklasse zu erstellen, die den tatsächlichen Zeiger kapselt. Der überladene Operator-> wird aufgerufen, macht alles, was er tut (z. B. Sperren für die Thread-Sicherheit), gibt den internen Zeiger zurück und der Compiler ruft dann -> für diesen internen Zeiger auf.

Die Konstanz wurde in den Kommentaren und anderen Antworten beantwortet (Sie können und sollten beides angeben).

Asaf
quelle