Frage

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 den Diamanten Problem, und oben Stück Code hat dieses Problem nicht.

Wie genau virtuelle Vererbung das Problem lösen?

Was verstehe ich: Als ich A *a = new D(); sagen, will der Compiler wissen, ob ein Objekt vom Typ D kann auf einen Zeiger vom Typ A zugeordnet werden, aber es hat zwei Wege, dass sie folgen können, aber nicht selbst entscheiden kann.

Also, wie virtuelle Vererbung beheben, um die Ausgabe (Hilfe-Compiler die Entscheidung treffen)?

War es hilfreich?

Lösung

Sie mögen: (Erreichbare mit virtueller Vererbung)

  A  
 / \  
B   C  
 \ /  
  D 

Und nicht: (Was ohne virtuelle Vererbung geschieht)

A   A  
|   |
B   C  
 \ /  
  D 

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

sein

Ihre Art D würde 2 VTable-Zeiger haben (Sie können sie im ersten Diagramm sehen), eine für B und eine für C, die praktisch erben A. D Objektgröße erhöht, da es 2 Zeiger speichert jetzt; jedoch gibt es nur eine A jetzt.

So B::A und C::A gleich sind und so kann es keine zweideutigen Anrufe von D sein. Wenn Sie nicht virtuelle Vererbung verwenden, müssen Sie das zweite Diagramm oben. Und jeder Anruf an ein Mitglied der A dann mehrdeutig wird, und Sie müssen angeben, welchen Weg Sie nehmen wollen.

Wikipedia hat einen weiteren guten Überblick und Beispiel hier

Andere Tipps

Instanzen von abgeleiteten Klassen „enthalten“ Instanzen von Basisklassen, so dass sie wie die im Speicher aussehen:

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

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

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

Also, note zwei "Kopien" von A-Daten. Virtuelle Vererbung bedeutet, dass innerhalb abgeleiteten Klasse gibt es einen VTable-Zeiger-Set zur Laufzeit, dass Punkte auf die Daten der Basisklasse, 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

Warum eine andere Antwort?

Nun, viele Beiträge auf SO und Artikel außerhalb sagen sich, dass Diamant Problem wird durch die Schaffung einzelne Instanz A gelöst anstelle von zwei (eine für jeden Elternteil D), so Mehrdeutigkeit aufzulösen. Dies ist jedoch nicht mir umfassendes Verständnis des Prozesses gegeben hat, landete ich mit noch mehr Fragen wie

nach oben
  1. was ist, wenn B und C versucht verschiedene Instanzen von A zum Beispiel zu erstellen parametrisierte Konstruktor mit unterschiedlichen Parametern (D::D(int x, int y): C(x), B(y) {}) nennen? Welche Instanz A wird gewählt, um einen Teil von D zu werden?
  2. was ist, wenn ich nicht-virtuelle Vererbung für B, sondern virtuell für C? Genügt es, für einzelne Instanz A in D erstellen?
  3. sollte ich immer virtuelle Vererbung von nun standardmäßig verwenden auf als vorbeugende Maßnahme, da es möglich Diamant Problem mit geringen Leistungskosten und keine weiteren Nachteile löst?

Nicht in der Lage zu sein Verhalten vorherzusagen, ohne Codebeispiele Mittel zu versuchen, das Konzept nicht verstehen. Unten ist das, was mir Kopf um virtuelle Vererbung zu wickeln geholfen.

Double A

Als erstes kann mit diesem Code ohne virtuelle Vererbung starten:

#include<iostream>
using namespace std;
class A {
public:
    A()                { cout << "A::A() "; }
    A(int x) : m_x(x)  { cout << "A::A(" << x << ") "; }
    int getX() const   { return m_x; }
private:
    int m_x = 42;
};

class B : public A {
public:
    B(int x):A(x)   { cout << "B::B(" << x << ") "; }
};

class C : public A {
public:
    C(int x):A(x) { cout << "C::C(" << x << ") "; }
};

class D : public C, public B  {
public:
    D(int x, int y): C(x), B(y)   {
        cout << "D::D(" << x << ", " << y << ") "; }
};

int main()  {
    cout << "Create b(2): " << endl;
    B b(2); cout << endl << endl;

    cout << "Create c(3): " << endl;
    C c(3); cout << endl << endl;

    cout << "Create d(2,3): " << endl;
    D d(2, 3); cout << endl << endl;

    // error: request for member 'getX' is ambiguous
    //cout << "d.getX() = " << d.getX() << endl;

    // error: 'A' is an ambiguous base of 'D'
    //cout << "d.A::getX() = " << d.A::getX() << endl;

    cout << "d.B::getX() = " << d.B::getX() << endl;
    cout << "d.C::getX() = " << d.C::getX() << endl;
}

Ermöglicht durch Ausgabe gehen. B b(2); Die Ausführung schafft A(2) wie erwartet, das gleiche für C c(3);:

Create b(2): 
A::A(2) B::B(2) 

Create c(3): 
A::A(3) C::C(3) 

muss D d(2, 3); sowohl B und C, jeder von ihnen seinen eigenen A zu schaffen, so dass wir Doppel A in d haben:

Create d(2,3): 
A::A(2) C::C(2) A::A(3) B::B(3) D::D(2, 3) 

Das ist der Grund für d.getX() zu verursachen Kompilierungsfehler als Compiler kann nicht wählen, welche A Instanz es Verfahren für nennen sollte. Dennoch ist es möglich, Methoden direkt gewählte Elternklasse aufzurufen:

d.B::getX() = 3
d.C::getX() = 2

Virtuality

Jetzt können virtuelle Vererbung hinzufügen. Unter Verwendung derselben Codebeispiel mit den folgenden Änderungen:

class B : virtual public A
...
class C : virtual public A
...
cout << "d.getX() = " << d.getX() << endl; //uncommented
cout << "d.A::getX() = " << d.A::getX() << endl; //uncommented
...

Lets Sprung zur Schaffung von d:

Create d(2,3): 
A::A() C::C(2) B::B(3) D::D(2, 3) 

Sie können sehen, ist A mit Standardkonstruktor erstellt Parameter ignoriert von Konstrukteuren von B und C geben. Als Mehrdeutigkeit gegangen ist, werden alle Anrufe an getX() den gleichen Wert zurück:

d.getX() = 42
d.A::getX() = 42
d.B::getX() = 42
d.C::getX() = 42

Aber was, wenn wir parametrisierte Konstruktor für A nennen wollen? Es kann explizit gemacht werden, indem sie von Konstruktor D Aufruf:

D(int x, int y, int z): A(x), C(y), B(z)

Normalerweise kann Klasse explizit Konstrukteuren der direkt Eltern verwendet nur, aber es ist ein Ausschluß für virtuelle Vererbung Fall. Die Entdeckung dieser Regel „angeklickt“ für mich und half zu verstehen virtuellen Schnittstellen viel:

Code-class B: virtual A bedeutet, dass jede Klasse von B geerbt ist jetzt verantwortlich für die Erstellung A von selbst, da B wird es nicht gehen automatisch.

Mit dieser Aussage im Sinne ist es einfach, alle Fragen zu beantworten, die ich hatte:

  1. Während D Schöpfung weder B noch C für Parameter von A verantwortlich ist, es ist völlig bis zu D nur.
  2. C wird die Schaffung von A zu D delegieren, aber B wird eine eigene Instanz von A schafft somit Diamanten Problem zurück
  3. bringen
  4. Definieren von Basisklasse Parameter in Enkelkind Klasse anstatt direkte Kind ist keine gute Praxis, so sollte es toleriert werden, wenn Diamant Problem besteht und diese Maßnahme ist unvermeidlich.

Das Problem ist nicht der Pfad die Compiler folgen müssen. Das Problem ist die Endpunkt von diesem Pfad: das Ergebnis der Besetzung. Wenn es darum geht Conversions zu geben, hat der Pfad keine Rolle, nur das Endergebnis der Fall ist.

Wenn Sie gewöhnliche Vererbung, jeder Weg seinen eigenen, unverwechselbaren Endpunkt hat, was bedeutet, dass das Ergebnis der Besetzung nicht eindeutig ist, was das Problem ist.

Wenn Sie virtuelle Vererbung verwenden, können Sie eine rautenförmige Hierarchie erhalten: beiden Wege führen zum gleichen Endpunkt. In diesem Fall ist das Problem des Weges nicht mehr existiert, von der Wahl (oder, genauer gesagt, keine Rolle mehr), da beiden Wege führen zum gleichen Ergebnis. Das Ergebnis ist nicht mehr zweideutig - das ist, was zählt. Der genaue Pfad nicht.

Eigentlich sollte das Beispiel wie folgt aussehen:

#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 der richtige ist gonna be: "EAT => D"

Virtuelle Vererbung löst nur die Duplizierung des Großvater! Aber Sie müssen noch die Methoden spezifizieren virtuellen sein, um die Methoden richtig ... overrided

zu bekommen
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top