474 Stimmen

Was ist in C++ eine virtuelle Basisklasse?

Ich möchte wissen, was ein " virtuelle Basisklasse " ist und was es bedeutet.

Lassen Sie mich ein Beispiel nennen:

class Foo
{
public:
    void DoSomething() { /* ... */ }
};

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};

591voto

OJ. Punkte 28189

Virtuelle Basisklassen, die bei der virtuellen Vererbung verwendet werden, sind eine Möglichkeit zu verhindern, dass mehrere "Instanzen" einer bestimmten Klasse in einer Vererbungshierarchie erscheinen, wenn Mehrfachvererbung verwendet wird.

Stellen Sie sich das folgende Szenario vor:

class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};

Die obige Klassenhierarchie führt zu dem "gefürchteten Diamanten", der wie folgt aussieht:

  A
 / \
B   C
 \ /
  D

Eine Instanz von D besteht aus B, das A enthält, und C, das ebenfalls A enthält. Sie haben also zwei "Instanzen" (in Ermangelung eines besseren Ausdrucks) von A.

Bei einem solchen Szenario besteht die Möglichkeit der Zweideutigkeit. Was passiert, wenn Sie dies tun:

D d;
d.Foo(); // is this B's Foo() or C's Foo() ??

Die virtuelle Vererbung ist dazu da, dieses Problem zu lösen. Wenn Sie bei der Vererbung Ihrer Klassen virtuell angeben, teilen Sie dem Compiler mit, dass Sie nur eine einzige Instanz wünschen.

class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};

Dies bedeutet, dass es nur eine "Instanz" von A in der Hierarchie gibt. Folglich

D d;
d.Foo(); // no longer ambiguous

Dies ist eine kurze Zusammenfassung. Für weitere Informationen lesen Sie bitte este y este . Ein gutes Beispiel ist auch verfügbar aquí .

299voto

paercebal Punkte 78198

Über das Speicherlayout

Nebenbei bemerkt, das Problem mit dem gefürchteten Diamanten ist, dass die Basisklasse mehrfach vorhanden ist. So mit regelmäßigen Vererbung, glauben Sie, Sie haben:

  A
 / \
B   C
 \ /
  D

Aber im Speicherlayout haben Sie das:

A   A
|   |
B   C
 \ /
  D

Dies erklärt, warum beim Aufruf von D::foo() haben Sie ein Zweideutigkeitsproblem. Aber die real Das Problem tritt auf, wenn Sie eine Mitgliedsvariable von A . Nehmen wir zum Beispiel an, wir haben:

class A
{
    public :
       foo() ;
       int m_iValue ;
} ;

Wenn Sie versuchen, auf die m_iValue de D wird der Compiler protestieren, denn er sieht in der Hierarchie zwei m_iValue nicht eine. Und wenn Sie einen ändern, sagen wir, B::m_iValue (das ist die A::m_iValue Elternteil von B ), C::m_iValue nicht geändert werden (das ist die A::m_iValue Elternteil von C ).

Hier kommt die virtuelle Vererbung ins Spiel, denn damit erhalten Sie wieder ein echtes Rautenlayout, bei dem nicht nur ein foo() Methode, sondern auch eine und nur eine m_iValue .

Was kann schon schiefgehen?

Stellen Sie sich vor:

  • A hat einige grundlegende Funktionen.
  • B fügt eine Art cooles Array von Daten hinzu (zum Beispiel)
  • C ein cooles Feature wie ein Beobachtermuster hinzufügt (z. B. bei m_iValue ).
  • D erbt von B y C und damit von A .

Bei normaler Vererbung ist die Änderung von m_iValue de D ist zweideutig, und dies muss geklärt werden. Selbst wenn dies der Fall ist, gibt es zwei m_iValues innerhalb D Sie sollten also daran denken und beide gleichzeitig aktualisieren.

Bei der virtuellen Vererbung wird die Änderung m_iValue de D ist ok... Aber... Nehmen wir an, Sie haben D . Durch seine C Schnittstelle haben Sie einen Beobachter angehängt. Und durch seine B Schnittstelle aktualisieren Sie das coole Array, was den Nebeneffekt hat, dass Sie direkt die m_iValue ...

Da die Veränderung der m_iValue direkt erfolgt (ohne Verwendung einer virtuellen Accessor-Methode), "hört" der Beobachter durch C wird nicht aufgerufen, da der Code, der das Abhören implementiert, in C y B weiß nichts davon...

Schlussfolgerung

Wenn Sie einen Diamanten in Ihrer Hierarchie haben, bedeutet das, dass Sie mit 95%iger Wahrscheinlichkeit etwas in dieser Hierarchie falsch gemacht haben.

38voto

lenkite Punkte 1092

Die Erläuterung der Mehrfachvererbung mit virtuellen Basen erfordert Kenntnisse des C++-Objektmodells. Und das Thema lässt sich am besten in einem Artikel und nicht in einem Kommentarfeld erklären.

Die beste und verständlichste Erklärung, die ich gefunden habe und die alle meine Zweifel zu diesem Thema ausgeräumt hat, ist dieser Artikel: http://www.phpcompiler.org/articles/virtualinheritance.html

Nachdem Sie das gelesen haben, brauchen Sie wirklich nichts mehr zu diesem Thema zu lesen (es sei denn, Sie sind Compiler-Autor)...

10voto

wilhelmtell Punkte 55189

Eine virtuelle Basisklasse ist eine Klasse, die nicht instanziiert werden kann: Sie können kein direktes Objekt aus ihr erstellen.

Ich glaube, Sie verwechseln da zwei sehr unterschiedliche Dinge. Virtuelle Vererbung ist nicht dasselbe wie eine abstrakte Klasse. Die virtuelle Vererbung verändert das Verhalten von Funktionsaufrufen; manchmal löst sie Funktionsaufrufe auf, die sonst mehrdeutig wären, manchmal verlagert sie die Behandlung von Funktionsaufrufen in eine andere Klasse als die, die man bei einer nicht-virtuellen Vererbung erwarten würde.

7voto

wilhelmtell Punkte 55189

Ich möchte mich den freundlichen Erklärungen von OJ anschließen.

Das virtuelle Erbe ist nicht ohne Preis. Wie bei allem, was virtuell ist, kommt es zu Leistungseinbußen. Es gibt einen Weg, diesen Leistungsabfall zu umgehen, der möglicherweise weniger elegant ist.

Anstatt den Diamanten zu brechen, indem man ihn virtuell ableitet, kann man eine weitere Ebene zum Diamanten hinzufügen, um etwas wie dieses zu erhalten:

   B
  / \
D11 D12
 |   |
D21 D22
 \   /
  DD

Keine der Klassen erbt virtuell, alle erben öffentlich. Die Klassen D21 und D22 werden dann die virtuelle Funktion f() verstecken, die für DD zweideutig ist, vielleicht indem sie die Funktion als privat deklarieren. Sie würden jeweils eine Wrapper-Funktion definieren, f1() bzw. f2(), die jeweils die klassenlokale (private) Funktion f() aufrufen, wodurch Konflikte gelöst werden. Die Klasse DD ruft f1() auf, wenn sie D11::f() will und f2(), wenn sie D12::f() will. Wenn Sie die Wrapper inline definieren, werden Sie wahrscheinlich keinen Overhead haben.

Wenn Sie D11 und D12 ändern können, können Sie natürlich den gleichen Trick innerhalb dieser Klassen anwenden, aber das ist oft nicht der Fall.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X