55
class A      { public: void eat(){ cout<<"A";} }; 
class B: virtual public A { public: void eat(){ cout<<"B";} }; 
class C: virtual public A { public: void eat(){ cout<<"C";} }; 
class D: public   B,C { public: void eat(){ cout<<"D";} }; 

int main(){ 
    A *a = new D(); 
    a->eat(); 
} 

Ich verstehe das Diamantproblem, und über Stück Code hat dieses Problem nicht.Wie löst virtuelle Vererbung die Mehrdeutigkeit "Diamant" (Mehrfachvererbung)?

Wie genau löst die virtuelle Vererbung das Problem?

Was ich verstehe: Als ich A *a = new D(); sagen, der Compiler, wenn ein Objekt vom Typ wissen will D kann auf einen Zeiger vom Typ A zugeordnet werden, aber es hat zwei Wege, die ihm folgen kann, aber kann sich nicht entscheiden von selbst.

Also, wie löst virtuelle Vererbung das Problem (Hilfe Compiler die Entscheidung treffen)?

Antwort

60

Sie wollen: (Erreichbare mit virtuellen Vererbung)

D 
/\ 
B C 
\/ 
    A 

Und nicht: (Was ohne virtuelle Vererbung geschieht)

D 
/\ 
B C 
| | 
A A 

Virtuelle Vererbung bedeutet, dass es nur sein 1 Beispiel der Basis A Klasse nicht 2.

Ihr Typ würde zwei vtable-Zeiger haben (Sie können sie im ersten Diagramm sehen), einen für B und einen für C, die praktisch A erben. 's Objektgröße ist erhöht, weil es jetzt 2 Zeiger speichert; aber es gibt nur eine A jetzt.

So B::A und C::A sind die gleichen und so kann es keine mehrdeutigen Anrufe von D geben. Wenn Sie keine virtuelle Vererbung verwenden, haben Sie das zweite Diagramm oben. Und jeder Aufruf an ein Mitglied von A wird dann mehrdeutig und Sie müssen angeben, welchen Pfad Sie verwenden möchten.

Wikipedia has another good rundown and example here

+0

Vtable Zeiger ist ein Implementierungsdetail. Nicht alle Compiler werden in diesem Fall vtable-Zeiger einführen. – curiousguy

+10

Ich denke, es würde besser aussehen, wenn die Graphen vertikal gespiegelt werden würden. In den meisten Fällen habe ich solche Vererbungsdiagramme gefunden, um die abgeleiteten Klassen unterhalb der Basen zu zeigen. (siehe "downcast", "upcast") – peterh

27

Instanzen von abgeleiteten Klassen „enthalten“ Instanzen von Basisklassen, also schauen sie in Erinnerung wie folgt aus:

class A: [A fields] 
class B: [A fields | B fields] 
class C: [A fields | C fields] 

So ohne virtuelle Vererbung, eine Instanz der Klasse D aussehen würde:

class D: [A fields | B fields | A fields | C fields | D fields] 
      '- derived from B -' '- derived from C -' 

Beachten Sie also zwei "Kopien" von A-Daten. Virtuelle Vererbung bedeutet, dass innerhalb abgeleiteten Klasse gibt es einen VTable-Zeiger-Set zur Laufzeit, die auf Daten der Basisklasse verweist, so dass Fälle von B, C und D-Klassen wie folgt aussehen:

class B: [A fields | B fields] 
      ^---------- pointer to A 

class C: [A fields | C fields] 
      ^---------- pointer to A 

class D: [A fields | B fields | C fields | D fields] 
      ^---------- pointer to B::A 
      ^--------------------- pointer to C::A 
+0

Wird der Vtable-Zeiger zur Laufzeit gesetzt? – Balu

7

Das Problem ist nicht die Weg muss der Compiler folgen. Das Problem ist der Endpunkt dieses Pfades: das Ergebnis der Besetzung. Wenn es um Typkonvertierungen geht, spielt der Pfad keine Rolle, nur das Endergebnis.

Wenn Sie gewöhnliche Vererbung verwenden, hat jeder Pfad seinen eigenen eindeutigen Endpunkt, was bedeutet, dass das Ergebnis der Besetzung mehrdeutig ist, was das Problem ist.

Wenn Sie die virtuelle Vererbung verwenden, erhalten Sie eine rautenförmige Hierarchie: Beide Pfade führen zum selben Endpunkt. In diesem Fall existiert das Problem der Pfadwahl nicht mehr (oder genauer gesagt, es spielt keine Rolle mehr), weil beide Pfade zum selben Ergebnis führen. Das Ergebnis ist nicht mehr zweideutig - darauf kommt es an. Der genaue Pfad nicht.

+0

@Andrey: Wie implementiert der Compiler die Vererbung ... Ich meine, ich bekomme Ihr Argument und ich möchte Ihnen danken, dass Sie es so klar erklärt haben..aber es würde wirklich helfen, wenn Sie es erklären (oder auf eine Referenz verweisen) zu, wie der Compiler tatsächlich Vererbung implementiert und was ändert sich, wenn ich virtuelle Vererbung tun – Bruce

5

Eigentlich ist das Beispiel wie folgt:

#include <iostream> 

//THE DIAMOND PROBLEM SOLVED!!! 
class A      { public: virtual ~A(){ } virtual void eat(){ std::cout<<"EAT=>A";} }; 
class B: virtual public A { public: virtual ~B(){ } virtual void eat(){ std::cout<<"EAT=>B";} }; 
class C: virtual public A { public: virtual ~C(){ } virtual void eat(){ std::cout<<"EAT=>C";} }; 
class D: public   B,C { public: virtual ~D(){ } virtual void eat(){ std::cout<<"EAT=>D";} }; 

int main(int argc, char ** argv){ 
    A *a = new D(); 
    a->eat(); 
    delete a; 
} 

... auf diese Weise die Ausgabe werde die richtige sein wird: „EAT => D“

Virtuelle Vererbung löst nur die Duplizierung der Großvater! aber Sie müssen noch die Methoden zu spezifizieren, um virtuell zu sein, die Methoden zu erhalten, richtig ... overrided

-2
#include <iostream> 

class A      { public: virtual ~A(){ } virtual void eat(){ std::cout<<"EAT=>A";} }; 
class B: virtual public A { public: virtual ~B(){ } virtual void eat(){ std::cout<<"EAT=>B";} }; 
class C: virtual public A { public: virtual ~C(){ } virtual void eat(){ std::cout<<"EAT=>C";} }; 
class D: public   B,C { public: virtual ~D(){ } }; 

int main(int argc, char ** argv){ 
    A *a = new D(); 
    a->eat(); 
    delete a; 
} 
+2

, wenn Sie die Funktion entfernen virtuelle void essen() {std :: cout <<"EAT=> D ";} von Klasse D kommt das DIAMOND-Problem wieder durch werfen Fehler Fehler: kein eindeutiger endgültiger Overrider für 'virtual void A :: essen()' in 'D' –

-1

Dieses Problem kann durch die Verwendung virtuelles Stichwortes lösen.

A 
/\ 
B C 
\/ 
    D 

Beispiel für Diamond Problem.

#include<stdio.h> 
using namespace std; 
class AA 
{ 
    public: 
      int a; 
     AA() 
      { 
       a=10; 
      } 
}; 
class BB: virtual public AA 
{ 
    public: 
      int b; 
     BB() 
      { 
       b=20; 
      } 
}; 
class CC:virtual public AA 
{ 
    public: 
      int c; 
     CC() 
      { 
       c=30; 
      } 
}; 
class DD:public BB,CC 
{ 
    public: 
      int d; 
     DD() 
      { 
       d=40; 
       printf("Value of A=%d\n",a);     
      } 
}; 
int main() 
{ 
    DD dobj; 
    return 0; 
} 
Verwandte Themen