
Popopom
Ich möchte wissen, was ein “virtuelle Basisklasse“ ist und was es bedeutet.
Lassen Sie mich ein Beispiel zeigen:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};

ABl.
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.
Betrachten Sie das folgende Szenario:
class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};
Die obige Klassenhierarchie ergibt den “gefürchteten Diamanten”, der so aussieht:
A
/ \
B C
\ /
D
Eine Instanz von D besteht aus B, das A enthält, und C, das auch A enthält. Sie haben also zwei “Instanzen” (mangels eines besseren Ausdrucks) von A.
Wenn Sie dieses Szenario haben, besteht die Möglichkeit von Mehrdeutigkeiten. Was passiert, wenn Sie dies tun:
D d;
d.Foo(); // is this B's Foo() or C's Foo() ??
Virtuelle Vererbung soll dieses Problem lösen. Wenn Sie beim Erben Ihrer Klassen virtual angeben, teilen Sie dem Compiler mit, dass Sie nur eine einzelne Instanz möchten.
class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};
Dies bedeutet, dass nur eine “Instanz” von A in der Hierarchie enthalten ist. Somit
D d;
d.Foo(); // no longer ambiguous
Dies ist eine Mini-Zusammenfassung. Weitere Informationen finden Sie unter Das und Das. Ein gutes Beispiel ist ebenfalls verfügbar Hier.

paercebal
Über das Speicherlayout
Nebenbei bemerkt, das Problem mit dem Dreaded Diamond ist, dass die Basisklasse mehrfach vorhanden ist. Bei regelmäßiger Erbschaft glauben Sie also, dass Sie Folgendes haben:
A
/ \
B C
\ /
D
Aber im Speicherlayout haben Sie:
A A
| |
B C
\ /
D
Dies erklärt warum, wenn Sie anrufen D::foo()
, Sie haben ein Mehrdeutigkeitsproblem. Aber die Real Problem tritt auf, wenn Sie eine Mitgliedsvariable von verwenden möchten A
. Nehmen wir zum Beispiel an, wir haben:
class A
{
public :
foo() ;
int m_iValue ;
} ;
Wann Sie versuchen, darauf zuzugreifen m_iValue
von D
wird der Compiler protestieren, weil er in der Hierarchie zwei sehen wird m_iValue
, nicht eins. Und wenn Sie einen ändern, sagen Sie: B::m_iValue
(das ist die A::m_iValue
Elternteil von B
), C::m_iValue
wird nicht geändert (das ist die A::m_iValue
Elternteil von C
).
Hier erweist sich die virtuelle Vererbung als praktisch, da Sie damit zu einem echten Diamant-Layout zurückkehren, mit nicht nur einem foo()
nur eine Methode, sondern auch eine und nur eine m_iValue
.
Was könnte schiefgehen?
Sich vorstellen:
A
hat einige grundlegende Funktionen.
B
fügt ihm eine Art cooles Array von Daten hinzu (zum Beispiel)
C
fügt ihm ein cooles Feature wie ein Beobachtermuster hinzu (z. B. on m_iValue
).
D
erbt von B
und C
und damit von A
.
Bei normaler Vererbung modifizieren m_iValue
von D
ist mehrdeutig und muss gelöst werden. Selbst wenn, es sind zwei m_iValues
Innerhalb D
also sollten Sie sich das besser merken und beide gleichzeitig aktualisieren.
Mit virtueller Vererbung, Modifizieren m_iValue
von D
ist ok… Aber… Sagen wir, du hast 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 es sich direkt ändert m_iValue
…
Als Änderung von m_iValue
erfolgt direkt (ohne Verwendung einer virtuellen Zugriffsmethode), wobei der Beobachter “zuhört”. C
wird nicht aufgerufen, da der Code, der das Abhören implementiert, in ist C
und B
weiß nichts davon…
Fazit
Wenn Sie eine Raute in Ihrer Hierarchie haben, bedeutet dies, dass Sie mit einer Wahrscheinlichkeit von 95 % etwas falsch mit dieser Hierarchie gemacht haben.

Lenkit
Das Erklären der Mehrfachvererbung mit virtuellen Basen erfordert Kenntnisse des C++-Objektmodells. Und das Thema anschaulich zu erklären geht am besten in einem Artikel und nicht in einem Kommentarfeld.
Die beste, lesbarste Erklärung, die ich gefunden habe und die alle meine Zweifel zu diesem Thema ausgeräumt 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 …
Eine virtuelle Basisklasse ist eine Klasse, die nicht instanziiert werden kann: Sie können daraus kein direktes Objekt erstellen.
Ich glaube du verwechselst zwei sehr unterschiedliche Dinge. Virtuelle Vererbung ist nicht dasselbe wie eine abstrakte Klasse. Virtuelle Vererbung modifiziert das Verhalten von Funktionsaufrufen; manchmal löst es Funktionsaufrufe auf, die sonst mehrdeutig wären, manchmal verschiebt es die Behandlung von Funktionsaufrufen auf eine andere Klasse als die, die man bei einer nicht virtuellen Vererbung erwarten würde.

wilhelmell
Ich möchte die freundlichen Erläuterungen von OJ ergänzen.
Virtuelle Erbschaft ist nicht ohne Preis. Wie bei allen virtuellen Dingen erhalten Sie einen Performance-Hit. Es gibt einen möglicherweise weniger eleganten Weg, um diesen Performance-Hit zu umgehen.
Anstatt den Diamanten durch virtuelles Ableiten zu brechen, können Sie dem Diamanten eine weitere Schicht hinzufügen, um so etwas zu erhalten:
B
/ \
D11 D12
| |
D21 D22
\ /
DD
Keine der Klassen erbt virtuell, alle erben öffentlich. Die Klassen D21 und D22 verbergen dann die virtuelle Funktion f(), die für DD mehrdeutig ist, vielleicht indem sie die Funktion als privat deklarieren. Sie würden jeweils eine Wrapper-Funktion f1() bzw. f2() definieren, die jeweils das klassenlokale (private) f() aufrufen und so Konflikte lösen. Die 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 keinen Overhead.
Wenn Sie D11 und D12 ändern können, können Sie natürlich den gleichen Trick in diesen Klassen ausführen, aber oft ist das nicht der Fall.

Luca Hermitte
Zusätzlich zu dem, was bereits über Mehrfach- und virtuelle Vererbung(en) gesagt wurde, gibt es einen sehr interessanten Artikel im Dr. Dobb’s Journal: Mehrfachvererbung als nützlich erachtet
Runnable-Verwendungsbeispiel für Diamond-Vererbung
Dieses Beispiel zeigt, wie eine virtuelle Basisklasse in einem typischen Szenario verwendet wird: zum Lösen von Diamond-Vererbungsproblemen.
Betrachten Sie das folgende Arbeitsbeispiel:
main.cpp
#include <cassert>
class A {
public:
A(){}
A(int i) : i(i) {}
int i;
virtual int f() = 0;
virtual int g() = 0;
virtual int h() = 0;
};
class B : public virtual A {
public:
B(int j) : j(j) {}
int j;
virtual int f() { return this->i + this->j; }
};
class C : public virtual A {
public:
C(int k) : k(k) {}
int k;
virtual int g() { return this->i + this->k; }
};
class D : public B, public C {
public:
D(int i, int j, int k) : A(i), B(j), C(k) {}
virtual int h() { return this->i + this->j + this->k; }
};
int main() {
D d = D(1, 2, 4);
assert(d.f() == 3);
assert(d.g() == 5);
assert(d.h() == 7);
}
Kompilieren und ausführen:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out
Wenn wir die entfernen virtual
hinein:
class B : public virtual A
Wir würden eine Wand mit Fehlern erhalten, dass GCC D-Mitglieder und Methoden, die zweimal über A geerbt wurden, nicht auflösen kann:
main.cpp:27:7: warning: virtual base ‘A’ inaccessible in ‘D’ due to ambiguity [-Wextra]
27 | class D : public B, public C {
| ^
main.cpp: In member function ‘virtual int D::h()’:
main.cpp:30:40: error: request for member ‘i’ is ambiguous
30 | virtual int h() { return this->i + this->j + this->k; }
| ^
main.cpp:7:13: note: candidates are: ‘int A::i’
7 | int i;
| ^
main.cpp:7:13: note: ‘int A::i’
main.cpp: In function ‘int main()’:
main.cpp:34:20: error: invalid cast to abstract class type ‘D’
34 | D d = D(1, 2, 4);
| ^
main.cpp:27:7: note: because the following virtual functions are pure within ‘D’:
27 | class D : public B, public C {
| ^
main.cpp:8:21: note: ‘virtual int A::f()’
8 | virtual int f() = 0;
| ^
main.cpp:9:21: note: ‘virtual int A::g()’
9 | virtual int g() = 0;
| ^
main.cpp:34:7: error: cannot declare variable ‘d’ to be of abstract type ‘D’
34 | D d = D(1, 2, 4);
| ^
In file included from /usr/include/c++/9/cassert:44,
from main.cpp:1:
main.cpp:35:14: error: request for member ‘f’ is ambiguous
35 | assert(d.f() == 3);
| ^
main.cpp:8:21: note: candidates are: ‘virtual int A::f()’
8 | virtual int f() = 0;
| ^
main.cpp:17:21: note: ‘virtual int B::f()’
17 | virtual int f() { return this->i + this->j; }
| ^
In file included from /usr/include/c++/9/cassert:44,
from main.cpp:1:
main.cpp:36:14: error: request for member ‘g’ is ambiguous
36 | assert(d.g() == 5);
| ^
main.cpp:9:21: note: candidates are: ‘virtual int A::g()’
9 | virtual int g() = 0;
| ^
main.cpp:24:21: note: ‘virtual int C::g()’
24 | virtual int g() { return this->i + this->k; }
| ^
main.cpp:9:21: note: ‘virtual int A::g()’
9 | virtual int g() = 0;
| ^
./main.out
Getestet auf GCC 9.3.0, Ubuntu 20.04.
9988800cookie-checkWas ist in C++ eine virtuelle Basisklasse?yes
Sollten wir virtuelle Basisklassen bei der „Mehrfachvererbung“ verwenden, denn wenn Klasse A eine Mitgliedsvariable int a hat und Klasse B auch eine Mitgliedsvariable int a hat und Klasse c die Klassen A und B erbt, wie entscheiden wir, welches „a“ verwendet werden soll?
– Namit Sinha
1. Mai 2014 um 19:01 Uhr
@NamitSinha nein, virtuelle Vererbung tut es nicht dieses Problem lösen. Das Mitglied a wäre ohnehin mehrdeutig
– Ichthyo
8. Dezember 2017 um 20:18 Uhr
@NamitSinha Virtuelle Vererbung ist kein magisches Werkzeug, um Mehrdeutigkeiten im Zusammenhang mit mehreren Vererbungen zu entfernen. Es “löst” ein “Problem”, mehr als einmal eine indirekte Basis zu haben. Was nur ein Problem darstellt, wenn es geteilt werden sollte (oft, aber nicht immer der Fall).
– Neugieriger
22. Dezember 2019 um 8:20 Uhr