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% 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:
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
).
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 )
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"):
(*p)
gibt das Objekt zurück, auf das p
zeigt. 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.
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"
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.
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:
Tags und Links c++ polymorphism