Warum behält der Dereferenzierungsoperator den Polymorphismus (späte Bindung) in C ++ bei?

8

Es ist bekannt, dass "virtuelle Dateien zur Laufzeit nur dann aufgelöst werden, wenn der Aufruf über a erfolgt Referenz oder Zeiger . "Daher ist es für mich überraschend, wenn der Dereferenzierungsoperator auch die dynamische Bindungsfunktion behält.

%Vor%

Die Ausgabe ist

%Vor%

Frage: Was hat der Compiler mit dem Dereferenzoperator * gemacht?

Ich dachte, es ist in der Kompilierzeit erledigt. Wenn also der Compiler den Zeiger p abstuft, sollte er annehmen, dass p auf ein Objekt vom Typ B zeigt. Zum Beispiel den folgenden Code

%Vor%

klagt

%Vor%     
Peng Zhang 19.05.2014, 22:14
quelle

5 Antworten

5

Der Derefencing / Indirection-Operator * tut selbst nichts. Wenn Sie beispielsweise nur *p; schreiben, ignoriert der Compiler möglicherweise diese Zeile, wenn p nur ein Zeiger ist.

Was% * macht, ändert die Semantik von Lesen und Schreiben:

%Vor%

Das *p = 0 bedeutet schreibt in das Objekt p zeigt auf . Beachten Sie, dass in C ++ ein Objekt ein Speicherbereich ist.

Ähnlich,

%Vor%

Hier bedeutet das Lesen von *p , dass den Wert des Objekts p auf zeigt.

Die Wertkategorie von *p bestimmt nur, welche Operationen die C ++ Sprache erlaubt auf Ausdrücke der Form *p .

Referenzen sind wirklich nur Hinweise auf syntaktischen Zucker. Wenn man also versucht, zu erklären, was *p mit Referenzen tut, ist das Zirkelschluss.

Betrachten wir leicht veränderte Klassen:

%Vor%

Seltsam, aber ich denke, das Speicherlayout sieht so aus:

%Vor%

Wenn wir von Derived* nach Base* konvertieren, kennt der Compiler den Offset des Subobjekts Base in einem Derived -Objekt, und kann den Adresswert für dieses Unterobjekt berechnen.

Der vtable-Zeiger wird gespeichert, für einzelne nichtvirtuelle Vererbung, im geringsten abgeleiteten Typ, der eine virtuelle Funktion hat. Es wird durch abgeleitete Klassen grob wie in dieser Implementierung / Simulation geändert.

Wenn wir jetzt

anrufen %Vor%

welches im C ++ Standard als

definiert ist %Vor%

Der Compiler weiß vom Typ pb (was Base* ist), dass wir eine virtuelle Funktion nennen. Daher bedeutet (*pb).say() den Eintrag für say in der vtable nachzuschlagen des Objekts pb zeigt auf und ruft es auf. Der Teil des Objekts pb zeigt auf ist was Polymorphismus erlaubt.

Auf der anderen Seite, wenn wir

kopieren %Vor%

Was passiert, ist, dass der Vtable-Zeiger nicht kopiert wird. Dies wäre gefährlich, weil Derived::say versuchen könnte, auf Derived::d zuzugreifen. Dieses Datenelement ist jedoch in einem Objekt vom Typ Base nicht verfügbar. was wir gerade erstellen (im copy ctor von Base ).

    
dyp 20.05.2014, 22:32
quelle
8

Auf der Oberfläche ist dies eine interessante Frage, da bei fehlender Überladung von unary * die Dereferenzierung zu einem lvalue B führt, kein Referenztyp. Aber selbst wenn man diese Argumentationslinie durchgeht, ist das ein Ablenkungsmanöver: Ausdrücke nie haben Referenztypen, da die Referenz sofort gelöscht wird und die Wertkategorie bestimmt. In diesem Sinne ist der unäre Operator * sehr ähnlich einer Funktion, die eine Referenz zurückgibt

Tatsächlich lautet die Antwort, dass Ihre anfängliche Assertion falsch ist: dynamic dispatch verlässt sich überhaupt nicht auf Referenzen oder Zeiger . Es sind Verweise und Zeiger, die es ermöglichen, slicing zu verhindern, aber sobald Sie einen Ausdruck haben, der sich auf Ihr polymorphes Objekt bezieht, wird jeder alte Funktionsaufruf funktionieren.

Bedenke auch:

%Vor%

( Live-Demo )

    
quelle
2

Nachdem ich einige Nachforschungen angestellt habe, denke ich, dass ich eine (zumindest für mich) vernünftige Antwort für diese Frage habe.

Annahmen (excerptiert oder paraphrasiert aus dem Buch "C ++ Primer 5"):

  1. Der Dereferenzierungsoperator * für einen Zeiger p, d. h. (*p) gibt das Objekt zurück, auf das p zeigt.
  2. Ein Objekt einer abgeleiteten Klasse D: public B hat logisch zwei Teile, einer ist ein Unterobjekt der Klasse B und der andere Teil hat Mitglieder der Klasse D. (Dies erklärt das "Slicing

Die virtual mechanism of C++ , die ich zur Unterstützung dieser Antwort verwendet habe, stammt aus einem Artikel 12.5 Die virtuelle Tabelle . Es überzeugt mich zumindest . Unten ist eine Zahl, die konzeptionell die *__vptr und die VTable s des Codes in unserer Frage zeigt.

Meine Erklärung.

%Vor%

Da p ein Zeiger auf B ist, liefert (*p) ein Objekt vom Typ B , d.h. das Unterobjekt von (*ptr) . Benennen Sie dieses Objekt vom Typ B als obj_b .

Der *__vptr von obj_b verweist jedoch auf die VTable von D . Also, wenn es anruft say() , der Funktionszeiger von say() in der VTable von D zeigt auf die Methode, die druckt "Hello D"

Experiment, das meine Erklärung unterstützt.

%Vor%

Weitere Anmerkung

Beim Aufruf einer Methode eines Objekts x hängt die Frage, ob der Polymorphismus (dynamisches Binden von Klassenmitgliedern) geschieht, davon ab, auf welche VTable *__vptr des Objekts zeigt, auf das es zeigt.

Wenn wir B obj_x(*p); (&obj_x)->say(); schreiben, ist die Ausgabe "Hello B". Dies liegt daran, dass obj_x ein vollständig neu konstruiertes Objekt vom Typ B ist, das den synthetisierten Copy-Konstruktor der Struktur B verwendet. Daher zeigt *__vptr von obj_x auf die VTable von B.

Dank der Hilfe von dyp haben wir eine simulation des virtuellen Versands dieser Frage . Falls die Webseite von Coiliru entfernt wurde, habe ich den Code hier gespeichert.

    
Peng Zhang 20.05.2014 21:52
quelle
1

Hier rufen Sie die (virtuelle) Funktion nicht mit

auf %Vor%

aber mit

%Vor%

Dasselbe, nur eine andere Notation. Sie rufen virtual function auf und lösen dynamicaly auf.

BEARBEITEN:

Für (*p).say() Compiler wird Folgendes gemacht:

  • aus der Variablen p load value (die Adresse von etwas, in diesem Fall Objekt vom Typ B oder Unterklasse)
  • ruft die Adresse eines virtuellen Tabellenzeigers ab (relativ zur obj-Adresse).
  • gehe zur Tabelle und rufe die Funktion say () auf (Polymorphie in act).
Nenad 19.05.2014 22:19
quelle
-2

*p ist eine Referenz auf ein B -Objekt.

    
Cheers and hth. - Alf 19.05.2014 22:17
quelle

Tags und Links