Was ist in C++ eine virtuelle Basisklasse?

Lesezeit: 11 Minuten

Was ist in C eine virtuelle Basisklasse
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() { /* ... */ }
};

  • 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

1647201612 779 Was ist in C eine virtuelle Basisklasse
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.

  • @Bohdan verwendet virtuelle Schlüsselwörter so viel wie weniger, denn wenn wir virtuelle Schlüsselwörter verwenden, wird ein Schwergewichtsmechanismus angewendet. Ihre Programmeffizienz wird also reduziert.

    – Sagar

    2. Februar 2014 um 14:05 Uhr

  • @ ABl. Ich möchte nur noch eines wissen, nachdem Sie das virtuelle Schlüsselwort verwendet haben, welche Instanz wie aufgerufen wird d.Foo() // is this B's Foo() or C's Foo() (after using virtual keyword)

    – Sagar

    2. Februar 2014 um 14:08 Uhr


  • @Viktor, es ist nicht wichtig, es gehört nicht dazu B oder C. Denken Sie nur, dass es sich um eine zusammengeführte Methode handelt, die tatsächlich dazu gehört grandfather A

    – Friedrich Gauss

    15. Februar 2014 um 16:39 Uhr

  • Ihr “gefürchtetes Diamant”-Diagramm ist verwirrend, obwohl es allgemein verwendet zu werden scheint. Dies ist eigentlich ein Diagramm, das Klassenvererbungsbeziehungen zeigt — nicht ein Objektlayout. Der verwirrende Teil ist, dass wenn wir verwenden virtual, dann sieht das Objektlayout wie der Diamant aus; und wenn wir nicht verwenden virtual dann sieht das Objektlayout wie eine Baumstruktur aus, die zwei enthält AS

    – MM

    23. Juli 2015 um 4:19 Uhr


  • Ich muss diese Antwort aus dem von MM beschriebenen Grund ablehnen – das Diagramm drückt das Gegenteil des Beitrags aus.

    – David Stein

    2. Oktober 2016 um 17:47 Uhr

Was ist in C eine virtuelle Basisklasse
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 Dwird 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 Cund 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 Dalso 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 Cund 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.

  • Ihr “was schief gehen könnte” ist auf den direkten Zugriff auf ein Basismitglied zurückzuführen, nicht auf Mehrfachvererbung. Entfernen Sie ‘B’ und Sie haben das gleiche Problem. Die Grundregel: ‘Wenn es nicht privat ist, sollte es virtuell sein’ vermeidet das Problem. m_iValue ist nicht virtuell und sollte daher privat sein

    – Chris Dodd

    17. Dezember 2009 um 15:32 Uhr

  • @Chris Dodd: Nicht genau. Was mit m_iValue passiert, wäre mit jedem Symbol passiert (zB typedef, Mitgliedsvariable, Mitgliedsfunktion, Umwandlung in die Basisklasse usw.). Dies ist wirklich ein Problem der Mehrfachvererbung, ein Problem, das Benutzer beachten sollten, um die Mehrfachvererbung korrekt zu verwenden, anstatt den Java-Weg zu gehen und zu dem Schluss zu kommen: “Mehrfachvererbung ist 100% böse, machen wir das mit Schnittstellen”.

    – Paercebal

    30. Oktober 2010 um 6:38 Uhr


  • Hallo, wenn wir ein virtuelles Schlüsselwort verwenden, gibt es nur eine Kopie von A. Meine Frage ist, woher wir wissen, ob es von B oder C kommt? Ist meine Frage überhaupt berechtigt?

    – Benutzer875036

    29. Juni 2014 um 18:40 Uhr

  • @ user875036: A kommt sowohl von B als auch von C. In der Tat ändert Virtualität einige Dinge (z. B. D ruft den Konstruktor von A auf, nicht B oder C). Sowohl B als auch C (und D) haben einen Zeiger auf A.

    – Paercebal

    30. Juni 2014 um 16:44 Uhr

  • FWIW, falls sich jemand wundert, Mitgliedsvariablen kann nicht be virtual — virtual ist ein Spezifizierer für Funktionen. SO-Referenz: stackoverflow.com/questions/3698831/…

    – rholmes

    23. September 2016 um 19:21 Uhr

1647201613 596 Was ist in C eine virtuelle Basisklasse
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.

1647201614 13 Was ist in C eine virtuelle Basisklasse
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.

  • Dabei geht es nicht um mehr oder weniger elegantes Auflösen von Mehrdeutigkeiten (dafür können Sie immer explizite xxx::-Angaben verwenden). Bei nicht-virtueller Vererbung hat jede Instanz der Klasse DD zwei unabhängige Instanzen von B. Sobald die Klasse ein einziges nicht statisches Datenelement hat, unterscheiden sich virtuelle und nicht virtuelle Vererbung nicht nur durch die Syntax.

    – Benutzer3489112

    25. Juni 2014 um 18:02 Uhr

  • @user3489112 Sobald … nichts. Virtuelle und nicht virtuelle Vererbung unterscheiden sich semantisch, Punkt.

    – Neugieriger

    27. November 2018 um 23:44 Uhr

1647201614 71 Was ist in C eine virtuelle Basisklasse
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

  • Dabei geht es nicht um mehr oder weniger elegantes Auflösen von Mehrdeutigkeiten (dafür können Sie immer explizite xxx::-Angaben verwenden). Bei nicht-virtueller Vererbung hat jede Instanz der Klasse DD zwei unabhängige Instanzen von B. Sobald die Klasse ein einziges nicht statisches Datenelement hat, unterscheiden sich virtuelle und nicht virtuelle Vererbung nicht nur durch die Syntax.

    – Benutzer3489112

    25. Juni 2014 um 18:02 Uhr

  • @user3489112 Sobald … nichts. Virtuelle und nicht virtuelle Vererbung unterscheiden sich semantisch, Punkt.

    – Neugieriger

    27. November 2018 um 23:44 Uhr

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.

  • 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?

    – Klick mich

    20. Januar 2017 um 11:57 Uhr


  • @SebTu ah danke, nur etwas, das ich vergessen habe, aus dem Kopieren und Einfügen zu entfernen, habe es jetzt entfernt. Das Beispiel sollte auch ohne sie aussagekräftig sein.

    – Ciro Santilli Путлер Капут 六四事

    20. Januar 2017 um 13:00 Uhr

998880cookie-checkWas ist in C++ eine virtuelle Basisklasse?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy