Ich weiß, dass der Compiler für jede Klasse, die eine virtuelle Funktion oder eine Klasse hat, die von einer Klasse mit einer virtuellen Funktion abgeleitet ist, zwei Dinge tut. Erstens erstellt es eine virtuelle Tabelle für diese Klasse und zweitens legt es einen virtuellen Zeiger (vptr) in den Basisabschnitt für das Objekt. Zur Laufzeit wird dieser vptr zugewiesen und beginnt, auf die richtige vtable zu zeigen, wenn das Objekt instanziiert wird.
Meine Frage ist, wo genau in diesem Instanziierungsprozess dieser vptr gesetzt wird? Erfolgt diese Zuweisung von vptr im Konstruktor des Objekts vor / nach dem Konstruktor?
Dies ist streng von der Implementierung abhängig.
Für die meisten Compiler
Der Compiler initialisiert dieses & gt; __ vptr in der Member-Initialisierungsliste jedes Konstruktors.
Der V-Zeiger jedes Objekts soll auf die V-Tabelle seiner Klasse zeigen, und der Compiler generiert den versteckten Code dafür und fügt ihn dem Konstruktorcode hinzu. Etwas wie:
%Vor%Diese C ++ - FAQ erklärt ein Kern dessen, was genau passiert.
Der Zeiger auf die vtable wird beim Eintritt in jeden Konstruktor in der Hierarchie und dann beim Eintritt jedes Destruktors aktualisiert. Der vptr beginnt auf die Basisklasse zu zeigen und wird dann aktualisiert, wenn die verschiedenen Ebenen initialisiert sind.
Sie werden zwar von vielen verschiedenen Leuten lesen, dass diese Implementierung definiert ist, da es die gesamte Auswahl an VTables ist, aber Tatsache ist, dass alle Compiler vTabellen verwenden, und wenn Sie einen vTable-Ansatz wählen, schreibt der Standard dies vor em> Der Typ des Laufzeitobjekts ist der des Konstruktors / Destruktors, der ausgeführt wird , und das wiederum bedeutet, dass der dynamische Dispatch-Mechanismus unabhängig davon, wie die Konstruktions- / Zerstörungskette durchlaufen wird, angepasst werden muss / p>
Betrachten Sie das folgende Code-Snippet:
%Vor% Der Standard schreibt vor, dass die Ausgabe dieses Programms base
, derived
, derived
, base
ist, aber der Aufruf in callback
ist der gleiche von allen vier Aufrufen der Funktion. Der einzige Weg, wie es implementiert werden kann, besteht darin, das vptr im Objekt zu aktualisieren, während die Konstruktion / Zerstörung fortschreitet.
Dieser msdn Artikel erklärt es in großen detali
Dort steht:
"Und die endgültige Antwort ist ... wie Sie es erwarten. Es passiert im Konstruktor."
Wenn ich direkt am Anfang des Konstruktors hinzufügen könnte, bevor irgendein anderer Code in Ihrem Konstruktor ausgeführt wird.
Aber seien Sie vorsichtig, nehmen wir an, Sie haben die Klasse A und eine von A1 abgeleitete Klasse A1.
"Hier ist die gesamte Sequenz von Ereignissen, wenn Sie eine Instanz der Klasse A1 konstruieren:
- A1 :: A1 ruft A :: A
auf- A :: A setzt vtable auf Vtable
von A- A :: A führt aus und gibt
zurück- A1 :: A1 setzt vtable auf A1s vtable
- A1 :: A1 führt aus und gibt "
zurück
Im Rumpf des Konstruktors können die virtuellen Funktionen aufgerufen werden. Wenn die Implementierung ein vptr
verwendet hat, ist das vptr
bereits festgelegt.
Beachten Sie, dass die in einem ctor aufgerufenen virtuellen Funktionen diejenigen sind, die in der Klasse dieses -Konstruktors definiert sind und nicht diejenigen, die möglicherweise von einer abgeleiteten Klasse überschrieben werden.
%Vor% Obwohl es implementierungsabhängig ist, müsste es tatsächlich geschehen, bevor der Rumpf des Konstruktors selbst ausgewertet wird, da Sie gemäß der C ++ - Spezifikation (12.7 / 3) über this
auf nicht statische Klassenmethoden zugreifen können. Zeiger im Konstruktor-Body ... daher müsste die V-Tabelle eingerichtet werden, bevor der Körper des Konstruktors aufgerufen wird, andernfalls würde der Aufruf von virtuellen Klassenmethoden über den this
Zeiger nicht korrekt funktionieren. Obwohl der this
-Zeiger und die vtable zwei verschiedene Dinge sind, demonstriert die Tatsache, dass der C ++ - Standard den this
-Zeiger im Rumpf eines Konstruktors verwendet, wie der Compiler die vtable für standardkonforme Verwendungen implementieren muss Der Zeiger this
funktioniert zumindest aus Timing-Perspektive korrekt. Wenn die vtable während oder nach dem Aufrufen des Konstruktorhauptteils initialisiert wurde, wäre es problematisch, den this
-Zeiger zum Aufrufen virtueller Funktionen im Rumpf des Konstruktors zu verwenden oder den this
-Zeiger an Funktionen zu übergeben, die vom dynamischen Versand abhängen undefiniertes Verhalten.