Bin mir nicht sicher, ob wir darunter das Gleiche verstehen, aber nein. Bzw. man kann
den Pointer im Speicher schon ändern (vorausgesetzt, das eigene OS verwendet überhaupt
Vpointer, müsste ja theoretisch auch nicht sein), aber ob dann alles in jedem Fall
funktioniert ist zu bezweifeln.
Genau so meine ich das
Eben nicht. Jede Cpp-Datei wird einzeln behandelt, und was in den
anderen Cpps (bzw. schon vorkompilierten Dingen) ist, ist unbekannt.
Wir meinen nicht dasselbe.
C++:
//Header
class A
{
public:
virtual void func();
int m_i;
};
class B : public A
{
public:
void func() override;
};
//Source / CPP
void A::func()
{
printf("Bla");
}
void B::func()
{
printf("Blubber");
}
Wenn jetzt B in einer Funktion instanziiert wird (B b
, dann schaut der Compiler nach, was B ist. Er sieht "class B : public A" und sagt sich "Beginnen wir mal mit A". Er schaut nach, was A ist und sieht: "Aha, eine Funktion virtual void func() und eine Membervariable m_i". Dann geht er zurück zu B und sieht "Oha, da wird die Funktion void func() überschrieben, das m_i bleibt aber."
Soweit einverstanden?
Ich würde sagen, der Compiler könnte hier gefahrlos die VTable wegoptimieren (wenn der Standard entsprechend kein implizites virtual hat natürlich).
Er könnte schauen "Aha, B ist A mit einer anderen Funktion, die aber gleich heisst. Da das override selbst aber kein explizites virtual hat, kann ich das als nicht-virtuelle Klasse betrachten, quasi die komplette Definition von virtual void A::func() mit der kompletten Definition ('override' ist ja eigentlich keine Definition, sondern nur ein Garant, dass tatsächlich etwas überschrieben wird) von void B::func() überschreiben."
-> Der Compiler muss die Definition der Klassen und die Deklaration der Memberfunktionen kennen, sonst wird er einen Fehler werfen.
Er muss somit nicht die cpp-Dateien kennen, um die Klassen optimieren zu können. (Klar kann er nicht alle Klasseninstanzen überprüfen gehen, aber er kann an der Wurzel sehr leicht ansetzen).
Interessante Wortunterscheidung. Bisher hab ich beide Worte einfach für Beides verwendet
Und wieder nein, In KIndklasse können Funktionen hinzugefügt werden, die (diesmal
ausgehend von der Kindklasse) virtual sind. Dann wird die Table wieder relevant.
"Vererbt an" bzw. "erbt von" erklärt es vielleicht besser.
Wenn die Kindklasse aber virtuelle Funktionen hat, wird sie eine (potentielle) Elternklasse.
Wenn sie aber keine virtuelle Funktionen hat (und der Standard override nicht als implizites virtual gelten würde), dann ist sie sicher keine (potentielle) Elternklasse.
Oder als Beispiel:
Code:
//vv = virtual function
//oo = override (non-virtual)
//vo = virtual override
//ff = non-virtual function
// | = Trenner Klassenzugehörigkeit
Klasse A: (Muss virtual sein, kann nicht optimiert werden)
Funcs: vv vv vv vv
Data: 0 - 3
Klasse B : A: (Muss virtual sein, kann nicht optimiert werden)
Funcs: vv vo vv oo | ff ff
Data: 0 - 3 | 4 - 15
Klasse C : A: (Muss nicht virtual sein, kann optimiert werden)
Funcs: oo oo oo oo | ff ff
Data: 0 - 3 | 4 - 7
Ganz ohne erbende Klassen, nur mit einer Klasse allein, die was Virtuelles hat,
gibts schon Table und Pointer, weil ja später was vererbt werden könnte.
Einverstanden.
Versteh ich leider nicht. (Codetags von mir, Code macht so aber keinen Sinn)
Äh ups
Es wird natürlich Derived::Func ausgeführt (dank vTable) und mit den Daten aus Derived.
Was denn?
In welchem Fall deine Cpp-Datei das brauchen kann?
In welchem Fall es die Cpp-Datei noch nicht geben muss?
Ersteres.
Wobei ich gerade beginne, am virtual-Konzept zu zweifeln. Ein Beispiel:
Code:
Derived d;
00106FAA lea ecx,[d]
00106FAD call Derived::Derived (0AEC6Eh) //_vfptr (so heisst die vTable in VS) wird erstellt
Base* b = (&d);
00106FB2 lea eax,[d] //Kopiert den Wert von d (_vfptr) nach EAX
00106FB5 mov dword ptr [b],eax //Überschreibt den _vfptr von Base mit dem von Derived
b->Func();
00106FB8 mov eax,dword ptr [b] //Bewegt d in das eax-Register
00106FBB mov edx,dword ptr [eax] //Bewegt __vfptr in das edx-Register
00106FBD mov esi,esp
00106FBF mov ecx,dword ptr [b] //Bewegt den _vfptr bzw. die Startadresse der Klasse von b nach ECX
00106FC2 mov eax,dword ptr [edx] //Bewegt _vfptr[0] nach EAX
00106FC4 call eax //Ruft _vfptr[0] auf (eax ist 0125C810h)
00106FC6 cmp esi,esp //So'n Debug-Zeugs halt :P
00106FC8 call __RTC_CheckEsp (0AD350h)
d.Func(); //Test
012B755D lea ecx,[d] //Für die ganz Findigen: Ja, das ist aus 2 verschiedenen Builds zusammengeklebt.
012B7560 call Derived::Func (0125C810h) //Stimmt überein
Eine Klasse ohne _vfptr:
Code:
Normal n;
002D747D lea ecx,[n]
002D7480 call Normal::Normal (027BD11h)
n.Func();
002D7485 lea ecx,[n]
002D7488 call Normal::Func (027DFEEh)
Soweit das, was geschieht. Nun mein Vorschlag: Anstatt jedem Objekt Ballast mitzugeben, könnte man doch einfach bei der Anweisung Base* b = &d; den Typ für künftige Operationen anpassen, i.e. alle virtual functions der Basisklasse durch die jeweiligen Overrides ersetzen. Also wenn die Klassen sind:
Code:
class Base {
public:
virtual void Func(){ printf("Bla"); }
virtual void Func2(){ }
};
class Derived : public Base {
public:
void Func() override{ printf("Blubber"); }
};
Dann kann der Compiler ja folgendes generieren:
Code:
Derived d;
Base* b = &d;
b->Func();
//mov ecx, dword ptr [b]
//call Derived::Func()
b->Func2();
//call Base::Func2();
Denn ganz offensichtlich ist es sonst ja doppelt gemoppelt: Die Adresse wird in __vfptr und statisch gefunden.
Und der Compiler kennt a) die Basisklasse und b) die erbende Klasse und damit die Funktionsadressen.
Ausser, ich hätte wie so oft etwas übersehen - es muss ja schon einen Grund geben, warum es so gemacht wird; er erschliesst sich mir nur nicht so ganz.
Mir schwirrt langsam der Kopf. Ist wie beim langen Studieren von einem Wort: Irgendwann versteht man es nicht mehr...
Gruss
cwriter