vtable
-
base.hxx
#ifndef _base_hxx_ #define _base_hxx_ class base { public: virtual void foo(); virtual void foo2(); virtual ~base(); }; #endifderived.hxx
#ifndef _derived_hxx_ #define _derived_hxx_ #include "base.hxx" class derived : public base { ~derived(); void foo(); }; #endifbase.cpp
#include "base.hxx" #include <iostream> using namespace std; void base::foo() { cout<<"base::foo()"<<endl; } void base::foo2() { cout<<"base::foo2()"<<endl; } base::~base() { cout<<"base::~base()"<<endl; }derived.cpp
#include "derived.hxx" #include <iostream> using namespace std; derived::~derived() { cout<<"derived::~derived()"<<endl; } void derived::foo() { cout<<"derived::foo()"<<endl; }main.cpp
#include "base.hxx" #include "derived.hxx" int main() { base *bp = new derived(); //was passiert hier intern? bp->foo(); bp->foo2(); delete bp; base *bp2 = new base(); bp2->foo(); bp2->foo2(); delete bp2; return 0; }meine Frage:
1. in Debug wenn:base *bp = new derived();aufgerufen wird.
in Watch-Fenster :
bp hat 2 Einträge
[derived] und __vfptr
in [derived] gibt es ein base Eintrag drunter wieder einmal __vfptr
diese __vfptr ist gleich wie o.g. __vfptr. wieso zeigt es 2 mal?
2. in __vfptr gibt es 3 Einträge, anscheinend __vfptr ist ein Zeiger auf ein pointer-Array.
[0]:derived::foo(void)
[1]:base::foo2(void)
[2]:derived::'scalar detecting destructor(unsigned int)'
aber was passiert wennbase *bp = new derived();aufgerufen wird?
Meine Vermutung war :
ein vtable wird erzeugt mit 3 Einträge[0] Adresse von base::foo()
[1] Adresse von base::foo2()
[2] Adresse von base::~base()dann wird "new derived()" aufgerufen, damit die Adress von base::foo überschreibt mit Adresse von derived::foo(), weil base::foo2() nicht von derived überschrieben, bleibt der Eintrag.Aber die Adresse von base::~base() wird auch überschrieben? dann haben wir am Ende
[0] Adresse von derived::foo()
[1] Adresse von base::foo2()
[2] Adresse von derived::~derived()dann
1. bp->foo(); //derived::foo() 2. bp->foo2(); //base::foo2() 3. delete bp; //derived::~derived()mit 3. stimmt es nicht, deswegen könnte es sein, statt base::~base() zu überschreiben, wird ein derived::~derived()davor hinzufügt. Also
Vtable sieht so aus (nach Instanzierung):[0] Adresse von derived::foo()
[1] Adresse von base::foo2()
[2] Adresse von derived::~derived()
[3] Adresse von base::~base()Ist meine Vermutung richtig??
-
Welchen Compiler benutzt du überhaupt? Die Implementation von vtables kann von Compiler zu Compiler abweichen.
Meistens ist ein vtable ein zur Compilezeit erstelltes Array von Methoden. Jede Klasse mit virtuellen Methoden (sowie davon abgeleitete Klassen) enthalten zusätzlich eine versteckte Objektvariable, welche die Adresse des zur Klasse gehörenden vtables enthält. Erzeugst du also in Objekt von 'base', zeigt diese Variable auf den vtable von 'base' und entsprechend bei einem Objekt von 'derived' auf dessen vtable. Der Aufruf von virtuellen Methoden ist auch anders als bei anderen. Sinngemäß könnte man do formulieren: "object->_vptr[1]();" würde z.B. die zweite virtuelle Methode des Objektes 'object' aufrufen, wenn _vptr ein Zeiger auf den vtable ist.
Die vtables deiner Klassen sehen ungefähr so aus:
bei 'base':
base::foo()
base::foo2()
base::~base()bei 'derived':
derived::foo()
base::foo2()
derived::~base()"object->foo();" ruft dann _vptr[0] auf, welches je nach Klasse auf 'base::foo' oder 'derived:foo' zeigt.
Ich hoffe, dass ich das verständlich rüberbringen konnte

PS: '.hxx' habe ich noch nie gesehen - ich kenne nur '.h' und '.hpp' :p
Hat das ne spezielle Bedeutung?
-
1. Compiler : VC7
2."object->foo();" ruft dann _vptr[0] auf, welches je nach Klasse auf 'base::foo' oder 'derived:foo' zeigt.
es erklärt aber nicht D'tor und es anscheind nur eine Tabelle existiert, aber 2 pointer auf die selbe Tabelle
Name Value -bp 0x00370a90 -[derived] {...} -[base] {...} -__vfptr 0x004410fc const derived::'vftable' |- [0] 0x00415a0a derived::foo(void) |- [1] 0x00415776 base::foo(void) |- [2] 0x00415ece derived::'scalar deleting destructor'unsigned int -__vfptr 0x004410fc const derived::'vftable' |- [0] 0x00415a0a derived::foo(void) |- [1] 0x00415776 base::foo(void) |- [2] 0x00415ece derived::'scalar deleting destructor'unsigned3. hxx zumindest kann ich unter vc benutzen, es ist nur ein ASCII text, "hxx, h, hpp, hyy, hxy" sollte egal sein
-
Es existiert nur ein vfptr für das Objekt, es wird wohl nur für jede Klasse gesondert angezeigt.
Welches Problem hast du mit dem Destruktor? Ich verstehe leider nicht, was du meinst.
-
bei 'base':
base::foo()
base::foo2()
base::~base()bei 'derived':
derived::foo()
base::foo2()
derived::~base()ok, verstehe, 'derived' soll so aussehen
derived::foo() base::foo2() derived::~derived() derived::~base()spielt die Reihenfolge eine Rolle? bzw, wie wird die Reihenfolge festgelegt
-
Die Reihenfolge ist bei VC++ die, in der du die Methoden deklarierst. Der vtable von 'derived' enthält den Destruktor dieser Klasse, wobei dieser dann am Ende noch den Destruktor von 'base' aufruft.
-
es muss zwischen
derived::~derived() derived::~base()eine Beziehung geben, sonst wird derived::~base() nicht automatisch aufgerufen wenn du hier delete bp aufrufst(implizit derived::~derived() aufrufen)
-
Es gibt kein "derived::~base();".
Das ganze läuft so:
base* b = new derived(); delete b; // ruft Destruktor über "b->_vfptr[2](...)", also "b->~derived(...)" auf. // intern sieht der Destruktor dann ungefähr so aus: derived::~derived (...) { // eigener Code, Speicherbereinigung etc. base::~base(); }
-
wenn es so ist, dann steht base::base nicht auf der Vtable von derived, richtig?
-
richtig
-
wenn ich ein derived class noch eine virtual funktion hinzufüge
virtual foo3();dann soll es in der vtable so aussehen:
bei 'derived': derived::foo() base::foo2() derived::foo3() derived::~derived()und bei
base *bp = new derived;aber ich zur Laufzeit einen Zeiger erzeugt, der auf diese Table zeigt.
dann soll ich auch kein problembp->foo3();aufrufen ohne downcast. Aber in der Praxis muss ich immer
static_cast<derived*>(bp)->foo3();schreiben, wieso??
-
Ganz einfach, weil foo3 nicht in der Basisklasse definiert ist. Du kannst so nur das aufrufen was auch in der Basisklasse definiert ist. Wenn du also auch foo3 mit einem Zeiger auf base aufrufen willst, musst du es auch in der Basisklasse definieren. Wenn du etwas ohne cast aufrufen willst musst du es auch definiert haben. Die Basisklasse kann ja nicht etwas kennen, was erst in einer abgeleiteten Klasse vorkommt.
-
Ganz einfach, weil foo3 nicht in der Basisklasse definiert ist. Du kannst so nur das aufrufen was auch in der Basisklasse definiert ist. Wenn du also auch foo3 mit einem Zeiger auf base aufrufen willst, musst du es auch in der Basisklasse definieren. Wenn du etwas ohne cast aufrufen willst musst du es auch definiert haben. Die Basisklasse kann ja nicht etwas kennen, was erst in einer abgeleiteten Klasse vorkommt.
nach der Theorie von vorhin muss es doch möglich sein.
wir reden jetzt über die beiden vtables
Zur Compilerzeit wird 2 vtables erzeugt, also statisch, existiert zu diesem Zeitpunkt noch keiner Zeiger zu den beiden Tabellen.
vtable von base:table 1 bei 'base':
[0] base::foo()
[1] base::foo2()
[2] base::~base()table2 bei 'derived':
[0] derived::foo()
[1] base::foo2()
[2] derived::foo3()
[3] derived::~derived()bei Aufruf:
base *bp = new derived;wird ein Zeiger this erzeugt, was auf table2 zeigt.Durch diesen Zeiger kann ich natürlich auch foo3()aufrufen, etwa this[2]->foo3();
-
Welche Funktionen wirklich zur Verfügung stehen, steht aber erst zur Laufzeit fest. Dein Compiler guckt nicht nach welche Funktionen er aufrufen kann, sondern welche definiert sind. Wenn du jetzt statt derived eine derived2-Klasse hast und diese hat foo4, nicht aber foo3 würde der aufruf nicht mehr klappen. Und wenn du sowas über ein switch-case-Konstrukt oder so entscheidest welche abgeleitete Klasse verwendet werden soll kann das nicht funktionieren, weil der Compiler das nicht zur Compile-Time auswerten kann. Dann musst du explizit den Typ angeben.
-
netrobot schrieb:
vtable von base:
table 1 bei 'base':
[0] base::foo()
[1] base::foo2()
[2] base::~base()table2 bei 'derived':
[0] derived::foo()
[1] base::foo2()
[2] derived::foo3()
[3] derived::~derived()die einträge der vtable der abgeleiteten klasse müssen nat. exakt mit denen der basisklasse korrespondieren. denn ein virtueller funktionsaufruf vberuht ja gerade darauf, dass ein bestimmter eintrag, welcher einer bestimmten virtuellen funktion zugeordnet ist, aufgerufen wird. folglich:
[0] derived::foo() [1] base::foo2() [2] derived::~derived() [3] derived::foo3()bei Aufruf:
base *bp = new derived;wird ein Zeiger this erzeugt, was auf table2 zeigt.Durch diesen Zeiger kann ich natürlich auch foo3()aufrufen, etwa this[2]->foo3();
das ist dem zeiger base aber nicht zu entnehmen. insbesondere ist der name foo3 überhaupt nicht in der basisklasse bekannt. letzlich fehlen hier eher ein paar grundlagen in OO. eine bestimmte implementation dazu zu benutzen, dies zu lernen, ist eher ungünstig.
-
ich verstehe nicht ganz, in manchen Dokumenten steht z.b.
base *bp = new derived;bp ist ein Zeiger von Type base, aber auf derived zeigt.
in asm ist bp ist einfach ein Variable, drin ist eine Adresse, z.B. 0x1000, ab dieser Adress vorwärts (increment) wurde class derived abgelegt. Hier ist "base"ein platzhalter ohne wirkliche Bedeutung.
ist es nicht so? oder kann jd von der asm Sicht erklären?
-
Mit "bp" wollte man wohl nur sagen, dass die Variable ein 'base'-'pointer' (also ein Pointer auf ein Objekt der Klasse 'base', eben 'bp') ist

derived ist doch nichts anderes, als eine Erweiterung von base. Man kann ein Objekt von derived wie ein Objekt von base benutzen, aber halt nicht umgekehrt.