Ich möchte wissen, was eine " virtuelle Basisklasse " ist und was sie bedeutet.
Lassen Sie mich ein Beispiel zeigen:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
c++
virtual-inheritance
Popopome
quelle
quelle
Antworten:
Virtuelle Basisklassen, die bei der virtuellen Vererbung verwendet werden, verhindern, dass bei Verwendung der Mehrfachvererbung mehrere "Instanzen" einer bestimmten Klasse in einer Vererbungshierarchie angezeigt werden.
Stellen Sie sich das folgende Szenario vor:
Die obige Klassenhierarchie führt zu dem "gefürchteten Diamanten", der so aussieht:
Eine Instanz von D besteht aus B, das A enthält, und C, das auch A enthält. Sie haben also zwei "Instanzen" (aus Mangel an einem besseren Ausdruck) von A.
Wenn Sie dieses Szenario haben, besteht die Möglichkeit von Mehrdeutigkeiten. Was passiert, wenn Sie dies tun:
Die virtuelle Vererbung soll dieses Problem lösen. Wenn Sie beim Erben Ihrer Klassen virtual angeben, teilen Sie dem Compiler mit, dass Sie nur eine einzige Instanz möchten.
Dies bedeutet, dass nur eine "Instanz" von A in der Hierarchie enthalten ist. Daher
Dies ist eine kleine Zusammenfassung. Lesen Sie dies und das , um weitere Informationen zu erhalten . Ein gutes Beispiel finden Sie auch hier .
quelle
virtual
, das Objektlayout wie der Diamant aussieht; und wenn wir nicht verwenden,virtual
dann sieht das Objektlayout wie eine Baumstruktur aus, die zweiA
s enthältÜber das Speicherlayout
Nebenbei bemerkt, das Problem mit dem Dreaded Diamond ist, dass die Basisklasse mehrmals vorhanden ist. Bei regelmäßiger Vererbung glauben Sie also, dass Sie:
Aber im Speicherlayout haben Sie:
Dies erklärt, warum Sie beim Anruf
D::foo()
ein Mehrdeutigkeitsproblem haben. Das eigentliche Problem tritt jedoch auf, wenn Sie eine Mitgliedsvariable von verwenden möchtenA
. Nehmen wir zum Beispiel an, wir haben:Wenn Sie für den Zugriff versuchen werden
m_iValue
ausD
, wird der Compiler protestieren, weil in der Hierarchie, wird es zwei sehenm_iValue
, nicht ein. Und wenn Sie beispielsweise eines ändernB::m_iValue
(das ist dasA::m_iValue
übergeordnete Element vonB
),C::m_iValue
wird es nicht geändert (das ist dasA::m_iValue
übergeordnete Element vonC
).Hier bietet sich die virtuelle Vererbung an, da Sie mit nicht nur einer
foo()
Methode, sondern auch nur einer einzigen Methode zu einem echten Diamant-Layout zurückkehren könnenm_iValue
.Was könnte schiefgehen?
Vorstellen:
A
hat einige grundlegende Funktionen.B
fügt eine Art cooles Array von Daten hinzu (zum Beispiel)C
Fügt eine coole Funktion wie ein Beobachtermuster hinzu (z. B. Einm_iValue
).D
erbt vonB
undC
und damit vonA
.Bei normaler Vererbung ist das Ändern
m_iValue
von nichtD
eindeutig und muss behoben werden. Selbst wenn dies der Fall ist, befinden sich zwei imm_iValues
InnerenD
. Denken Sie also besser daran und aktualisieren Sie die beiden gleichzeitig.Bei der virtuellen Vererbung ist das Ändern
m_iValue
vonD
in Ordnung ... Aber ... Nehmen wir an, Sie habenD
. Über dieC
Benutzeroberfläche haben Sie einen Beobachter angehängt. Und über seineB
Benutzeroberfläche aktualisieren Sie das coole Array, was den Nebeneffekt hat, dass es sich direkt ändertm_iValue
...Da die Änderung
m_iValue
direkt (ohne Verwendung einer virtuellen Zugriffsmethode) erfolgt, wird der Beobachter, der "durchhört"C
, nicht aufgerufen, da der Code, der das Abhören implementiert, vorhanden istC
undB
nichts davon weiß ...Fazit
Wenn Sie einen Diamanten in Ihrer Hierarchie haben, bedeutet dies, dass Sie mit einer Wahrscheinlichkeit von 95% etwas mit dieser Hierarchie falsch gemacht haben.
quelle
Das Erklären der Mehrfachvererbung mit virtuellen Basen erfordert Kenntnisse des C ++ - Objektmodells. Eine klare Erklärung des Themas erfolgt am besten in einem Artikel und nicht in einem Kommentarfeld.
Die beste, lesbare Erklärung, die alle meine Zweifel zu diesem Thema gelöst hat, war dieser Artikel: http://www.phpcompiler.org/articles/virtualinheritance.html
Sie müssen wirklich nichts anderes zu diesem Thema lesen (es sei denn, Sie sind ein Compiler-Autor), nachdem Sie das gelesen haben ...
quelle
Ich denke, Sie verwechseln zwei sehr unterschiedliche Dinge. Virtuelle Vererbung ist nicht dasselbe wie eine abstrakte Klasse. Die virtuelle Vererbung ändert das Verhalten von Funktionsaufrufen. Manchmal werden Funktionsaufrufe aufgelöst, die ansonsten nicht eindeutig wären. Manchmal wird die Funktionsaufrufbehandlung auf eine andere Klasse verschoben, als dies bei einer nicht virtuellen Vererbung zu erwarten wäre.
quelle
Ich möchte zu OJs freundlichen Klarstellungen hinzufügen.
Virtuelle Vererbung ist nicht ohne Preis. Wie bei allen virtuellen Dingen erhalten Sie einen Leistungseinbruch. Es gibt einen Weg um diesen Performance-Hit, der möglicherweise weniger elegant ist.
Anstatt den Diamanten durch virtuelles Ableiten zu brechen, können Sie dem Diamanten eine weitere Schicht hinzufügen, um Folgendes zu erhalten:
Keine der Klassen erbt virtuell, alle erben öffentlich. Die Klassen D21 und D22 verbergen dann die virtuelle Funktion f (), die für DD nicht eindeutig ist, möglicherweise indem die Funktion als privat deklariert wird. Sie würden jeweils eine Wrapper-Funktion definieren, f1 () bzw. f2 (), die jeweils klassenlokales (privates) f () aufrufen und so Konflikte lösen. Klasse DD ruft f1 () auf, wenn sie D11 :: f () will, und f2 (), wenn sie D12 :: f () will. Wenn Sie die Wrapper inline definieren, erhalten Sie wahrscheinlich einen Overhead von etwa Null.
Wenn Sie D11 und D12 ändern können, können Sie natürlich den gleichen Trick in diesen Klassen ausführen, aber oft ist dies nicht der Fall.
quelle
Zusätzlich zu dem, was bereits über multiple und virtuelle Vererbung (en) gesagt wurde, gibt es einen sehr interessanten Artikel in Dr. Dobbs Journal: Multiple Vererbung als nützlich erachtet
quelle
Du bist ein bisschen verwirrend. Ich weiß nicht, ob Sie einige Konzepte verwechseln.
Sie haben keine virtuelle Basisklasse in Ihrem OP. Sie haben nur eine Basisklasse.
Sie haben eine virtuelle Vererbung durchgeführt. Dies wird normalerweise bei der Mehrfachvererbung verwendet, sodass mehrere abgeleitete Klassen die Mitglieder der Basisklasse verwenden, ohne sie zu reproduzieren.
Eine Basisklasse mit einer rein virtuellen Funktion wird nicht instanziiert. Dies erfordert die Syntax, mit der Paul arbeitet. Es wird normalerweise verwendet, damit abgeleitete Klassen diese Funktionen definieren müssen.
Ich möchte das nicht mehr erklären, weil ich nicht ganz verstehe, was Sie verlangen.
quelle
Dies bedeutet, dass ein Aufruf einer virtuellen Funktion an die "richtige" Klasse weitergeleitet wird.
C ++ FAQ Lite FTW.
Kurz gesagt, es wird häufig in Szenarien mit Mehrfachvererbung verwendet, in denen eine "Diamant" -Hierarchie gebildet wird. Durch die virtuelle Vererbung wird dann die in der unteren Klasse erzeugte Mehrdeutigkeit aufgehoben, wenn Sie die Funktion in dieser Klasse aufrufen und die Funktion entweder in die Klasse D1 oder D2 über dieser unteren Klasse aufgelöst werden muss. In der FAQ finden Sie ein Diagramm und Details.
Es wird auch in der Schwesterdelegation verwendet , eine leistungsstarke Funktion (wenn auch nicht für schwache Nerven). Siehe diese FAQ.
Siehe auch Punkt 40 in Effective C ++ 3. Ausgabe (43 in 2. Ausgabe).
quelle
Ausführbares Verwendungsbeispiel für die Diamond-Vererbung
Dieses Beispiel zeigt, wie eine virtuelle Basisklasse im typischen Szenario verwendet wird: Lösen der Diamantvererbung.
quelle
assert(A::aDefault == 0);
von der Hauptfunktion gibt mir einen Kompilierungsfehler:aDefault is not a member of A
mit gcc 5.4.0. Was soll es tun?Virtuelle Klassen sind nicht dasselbe wie virtuelle Vererbung. Virtuelle Klassen, die Sie nicht instanziieren können, virtuelle Vererbung ist etwas ganz anderes.
Wikipedia beschreibt es besser als ich kann. http://en.wikipedia.org/wiki/Virtual_inheritance
quelle
Regelmäßige Vererbung
Bei der typischen 3-Ebenen-Vererbung ohne Diamanten ohne virtuelle Vererbung wird beim Instanziieren eines neuen, am meisten abgeleiteten Objekts new aufgerufen und die für das Objekt erforderliche Größe vom Compiler aus dem Klassentyp aufgelöst und an new übergeben.
neu hat eine Unterschrift:
Und ruft an
malloc
und gibt den leeren Zeiger zurückDies wird dann an den Konstruktor des am meisten abgeleiteten Objekts übergeben, der sofort den mittleren Konstruktor und dann den mittleren Konstruktor sofort den Basiskonstruktor aufruft. Die Basis speichert dann einen Zeiger auf ihre virtuelle Tabelle am Anfang des Objekts und anschließend auf seine Attribute. Dies kehrt dann zum mittleren Konstruktor zurück, der seinen virtuellen Tabellenzeiger an derselben Stelle und dann seine Attribute nach den Attributen speichert, die vom Basiskonstruktor gespeichert worden wären. Es kehrt zum am meisten abgeleiteten Konstruktor zurück, der einen Zeiger auf seine virtuelle Tabelle am selben Speicherort und dann seine Attribute nach den Attributen speichert, die vom mittleren Konstruktor gespeichert worden wären.
Da der virtuelle Tabellenzeiger überschrieben wird, ist der virtuelle Tabellenzeiger immer die am meisten abgeleitete Klasse. Virtualität breitet sich in Richtung der am meisten abgeleiteten Klasse aus. Wenn eine Funktion in der Mittelklasse virtuell ist, ist sie in der am meisten abgeleiteten Klasse virtuell, jedoch nicht in der Basisklasse. Wenn Sie eine Instanz der am meisten abgeleiteten Klasse polymorph in einen Zeiger auf die Basisklasse umwandeln, löst der Compiler dies nicht in einen indirekten Aufruf der virtuellen Tabelle auf und ruft stattdessen die Funktion direkt auf
A::function()
. Wenn eine Funktion für den Typ, in den Sie sie umgewandelt haben, virtuell ist, wird sie in einen Aufruf der virtuellen Tabelle aufgelöst, die immer die der am meisten abgeleiteten Klasse ist. Wenn es für diesen Typ nicht virtuell ist, ruft es einfachType::function()
den Objektzeiger auf und übergibt ihn an ihn.Wenn ich Zeiger auf seine virtuelle Tabelle sage, ist es tatsächlich immer ein Versatz von 16 in die virtuelle Tabelle.
virtual
wird in mehr abgeleiteten Klassen nicht erneut benötigt, wenn es in einer weniger abgeleiteten Klasse virtuell ist, weil es sich ausbreitet. Es kann jedoch verwendet werden, um zu zeigen, dass die Funktion tatsächlich eine virtuelle Funktion ist, ohne die Klassen überprüfen zu müssen, die die Typdefinitionen erbt.override
ist ein weiterer Compiler-Guard, der besagt, dass diese Funktion etwas überschreibt, und wenn dies nicht der Fall ist, wird ein Compiler-Fehler ausgegeben.= 0
bedeutet, dass dies eine abstrakte Funktion istfinal
verhindert, dass eine virtuelle Funktion erneut in einer abgeleiteten Klasse implementiert wird, und stellt sicher, dass die virtuelle Tabelle der am meisten abgeleiteten Klasse die endgültige Funktion dieser Klasse enthält.= default
macht in der Dokumentation deutlich, dass der Compiler die Standardimplementierung verwendet= delete
Geben Sie einen Compilerfehler aus, wenn versucht wird, dies aufzurufenVirtuelle Vererbung
Erwägen
Ohne die Bassklasse virtuell zu erben, erhalten Sie ein Objekt, das so aussieht:
An Stelle von:
Dh es wird 2 Basisobjekte geben.
In der virtuellen Situation über Vererbung Diamant, nach neuen genannt wird, ruft es die meisten abgeleitete Konstruktor und in diesem Konstruktor, ruft es alle 3 abgeleitet Konstrukteurs - Offsets in die virtuelle Tabelle Tabelle vorbei, statt nur anrufen , anrufen
DerivedClass1::DerivedClass1()
undDerivedClass2::DerivedClass2()
und jene dann beide BerufungBase::Base()
Das Folgende wird alle im Debug-Modus -O0 kompiliert, sodass eine redundante Assembly erfolgt
Es ruft
Base::Base()
mit einem Zeiger auf den Objektversatz 32 auf. Base speichert einen Zeiger auf seine virtuelle Tabelle an der Adresse, die es empfängt, und auf seine Mitglieder danach.DerivedDerivedClass::DerivedDerivedClass()
ruft dannDerivedClass1::DerivedClass1()
mit einem Zeiger auf den Objektoffset 0 auf und übergibt auch die Adresse vonVTT for DerivedDerivedClass+8
DerivedDerivedClass::DerivedDerivedClass()
übergibt dann die Adresse des Objekts + 16 und die Adresse für VTTDerivedDerivedClass+24
zuDerivedClass2::DerivedClass2()
den Montag identisch ist zuDerivedClass1::DerivedClass1()
der Linie ausnehmen ,mov DWORD PTR [rax+8], 3
die offensichtlich ein 4 anstelle von 3 für besitztd = 4
.Danach werden alle 3 virtuellen Tabellenzeiger im Objekt durch Zeiger auf Offsets in
DerivedDerivedClass
der vtable der Darstellung für diese Klasse ersetzt.d->VirtualFunction();
::d->DerivedCommonFunction();
::d->DerivedCommonFunction2();
::d->DerivedDerivedCommonFunction();
::((DerivedClass2*)d)->DerivedCommonFunction2();
::((Base*)d)->VirtualFunction();
::quelle