Domanda

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(); 
} 

I capire il problema di diamante, e al di sopra pezzo di codice non ha questo problema.

Come funziona esattamente l'ereditarietà virtuale risolve il problema?

Quello che ho capito: Quando dico A *a = new D();, il compilatore vuole sapere se un oggetto di tipo D può essere assegnato ad un puntatore di tipo A, ma ha due percorsi che si può seguire, ma non può decidere da solo.

Quindi, come fa virtuale eredità risolvere il problema (aiuto compilatore prendere la decisione)?

È stato utile?

Soluzione

Si desidera: (ottenibile con l'ereditarietà virtuale)

  A  
 / \  
B   C  
 \ /  
  D 

E non: (Quello che succede senza eredità virtuale)

A   A  
|   |
B   C  
 \ /  
  D 

mezzi di eredità virtuale che ci sarà solo 1 istanza della classe base A non 2.

Il tuo tipo D avrebbe 2 puntatori vtable (potete vederli nel primo schema), uno per B e uno per C che A praticamente ereditare. formato oggetto di D è aumentata perché memorizza 2 puntatori ora; tuttavia v'è un solo A ora.

Così B::A e C::A sono gli stessi e quindi non ci può essere nessuna chiamata ambigue da D. Se non si utilizza l'ereditarietà virtuale si ha il secondo schema di cui sopra. E ogni chiamata a un membro di A diventa allora ambigua ed è necessario specificare quale percorso si vuole prendere.

Wikipedia è un altro buon esempio fatiscente e qui

Altri suggerimenti

Le istanze delle classi derivate "contiene" le istanze di classi base, in modo da apparire nella memoria del genere:

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

Così, senza eredità virtuale, un'istanza della classe D sarà simile:

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

, nota due "copie" di A dati. mezzi di eredità virtuale che classe derivata all'interno v'è un puntatore set vtable in fase di esecuzione che punta a dati della classe base, in modo che le istanze delle classi B, C e D assomigliano:

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

Perché un'altra risposta?

Bene, molti messaggi su SO e articoli dire all'esterno, che problema diamante viene risolto creando singola istanza A invece di due (uno per ciascun genitore di D), risolvendo così ambiguità. Tuttavia, questo non mi ha dato comprensione globale del processo, ho finito con ancora più domande come

  1. che cosa se B e C cerca di creare diverse istanze di A esempio chiamando parametrizzata costruttore con parametri diversi (D::D(int x, int y): C(x), B(y) {})? Quale esempio di A saranno scelti a far parte di D?
  2. Che cosa succede se io uso l'eredità non virtuale per B, ma una virtuale per C? E 'sufficiente per la creazione di singola istanza di A in D?
  3. devo sempre utilizzare l'ereditarietà virtuale di default da ora in poi come misura preventiva in quanto risolve problema possibile diamante con prestazione di costo minore e altri inconvenienti?

Non essendo in grado di prevedere il comportamento senza cercare esempi di codice mezzo che non sia la comprensione del concetto. Qui di seguito è quello che mi ha aiutato ad avvolgere la testa intorno eredità virtuale.

Doppio A

In primo luogo, permette di iniziare con questo codice senza eredità virtuale:

#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;
}

Consente di passare attraverso l'uscita. Esecuzione B b(2); crea A(2) come previsto, lo stesso per C c(3);:

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

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

D d(2, 3); ha bisogno sia B e C, ognuno di loro creando un proprio A, quindi dobbiamo doppia A in d:

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

Questa è la ragione per d.getX() di errore di compilazione causa come compilatore non può scegliere quale istanza A dovrebbe chiamare il metodo per. Ancora è possibile chiamare i metodi direttamente per classe genitore scelto:

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

Virtuality

Ora lascia aggiungere eredità virtuale. Utilizzando esempio di codice stesso con le seguenti modifiche:

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
...

Consente di salto per la creazione di d:

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

Si può vedere, A viene creato con il costruttore predefinito ignorando i parametri passati da costruttori di B e C. Come l'ambiguità è andato, tutte le chiamate al getX() restituiscono lo stesso valore:

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

Ma cosa succede se vogliamo chiamare il costruttore parametrizzata per A? Esso può essere fatto chiamando esplicitamente dal costruttore della D:

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

In genere, classe può utilizzare in modo esplicito costruttori di soli genitori diretti, ma c'è un'esclusione per caso eredità virtuale. Alla scoperta di questa regola "cliccato" per me e ha contribuito a comprendere interfacce virtuali molto:

codice indica class B: virtual A, che ogni classe ereditata da B è ora responsabile per la creazione di A di per sé, in quanto B non ha intenzione di farlo automaticamente.

Con questa affermazione in mente è facile rispondere a tutte le domande che avevo:

  1. Durante la creazione né DB C è responsabile per i parametri di A, è totalmente a D solo.
  2. C delegherà creazione di A a D, ma B creerà la propria istanza di A portando così problema Diamond Back
  3. Definizione dei parametri della classe base in classe nipote piuttosto che figlio diretto non è una pratica buona, quindi dovrebbe essere tollerata quando esiste problema dei diamanti e questa misura è inevitabile.

Il problema non è il percorso il compilatore deve seguire. Il problema è il endpoint di quel percorso: il risultato del cast. Quando si tratta di conversioni di tipo, il percorso non ha importanza, solo il risultato finale fa.

Se si utilizza l'ereditarietà ordinaria, ogni percorso ha il suo punto finale distintivo, il che significa che il risultato del cast è ambiguo, che è il problema.

Se si utilizza l'ereditarietà virtuale, si ottiene una gerarchia a forma di diamante: entrambi i percorsi porta allo stesso endpoint. In questo caso esiste il problema di scegliere la strada non è più (o, più precisamente, non ha più importanza), in quanto entrambi i percorsi conducono allo stesso risultato. Il risultato non è più ambigua - che è ciò che conta. Il percorso esatto non lo fa.

In realtà l'esempio dovrebbe essere la seguente:

#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;
}

... in questo modo l'uscita è gonna essere quella giusta: "EAT => D"

eredità virtuale risolve solo la duplicazione del nonno! Ma è comunque necessario specificare i metodi per essere virtuale al fine di ottenere i metodi correttamente overrided ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top