Pergunta

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

Eu entendo o diamante problema, e acima pedaço de código que não tem esse problema.

Não exatamente como herança virtual resolver o problema?

O que eu entendo: Quando eu digo A *a = new D();, o compilador quer saber se um objeto do tipo D pode ser atribuído a um ponteiro do tipo A, mas ele tem dois caminhos que ele pode seguir, mas não pode decidir por si só.

Então, como herança virtual resolver o problema (o compilador de ajuda tomar a decisão)?

Foi útil?

Solução

Você quer: (Alcançável com herança virtual)

  A  
 / \  
B   C  
 \ /  
  D 

E não: (O que acontece sem herança virtual)

A   A  
|   |
B   C  
 \ /  
  D 

Herança virtual significa que haverá apenas 1 instância da base A classe não 2.

Seu tipo D teria 2 ponteiros vtable (você pode vê -los no primeiro diagrama), um para B e um para C que praticamente herdam A. DO tamanho do objeto do objeto é aumentado porque armazena 2 ponteiros agora; No entanto, existe apenas um A agora.

Então B::A e C::A são iguais e, portanto, não pode haver chamadas ambíguas de D. Se você não usa a herança virtual, terá o segundo diagrama acima. E qualquer chamada para um membro de A então se torna ambígua e você precisa especificar qual caminho deseja seguir.

A Wikipedia tem outro bom resumo e exemplo aqui

Outras dicas

Instâncias de classes derivadas "contêm" instâncias de classes base, para que eles olhem na memória assim:

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

Assim, sem herança virtual, a instância da classe D seria:

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

Portanto, observe duas "cópias" de dados. A herança virtual significa que, na classe Derivada, há um ponteiro vtable definido em tempo de execução que aponta para dados da classe base, para que as instâncias das classes B, C e D se parecem:

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

Por uma outra resposta?

Bem, muitos posts sobre ISSO e artigos fora dizer, que o diamante problema é resolvido através da criação de instância única de A em vez de dois (um para cada um dos pais de D), assim resolver a ambiguidade.No entanto, isso não me dê compreensão abrangente do processo, acabei com ainda mais perguntas como

  1. o que se B e C tenta criar diferentes instâncias de A exemplo:chamando parametrizando o construtor com parâmetros diferentes (D::D(int x, int y): C(x), B(y) {})?A instância do A será escolhido para se tornar parte de D?
  2. e se eu usar o não-virtual herança para B, mas virtual, para C?É o suficiente para criar uma única instância de A no D?
  3. devo sempre usar o virtual herança por padrão a partir de agora, como medida preventiva, pois resolve possível diamante problema com o menor custo de desempenho e sem outros inconvenientes?

Não sendo capaz de predizer o comportamento sem tentar exemplos de código significa não compreender o conceito.Abaixo está o que me ajudou a envolver a cabeça em torno de herança virtual.

Dupla

Primeiro, vamos começar com este código, sem herança virtual:

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

Vamos passar por saída.Execução B b(2); cria A(2) como esperado, mesmo para 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); precisa tanto de B e C, cada um deles a criação de seus próprios A, por isso temos de casal A no d:

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

Essa é a razão para d.getX() a causa erro de compilação como compilador não pode escolher o que A instância, ele deve chamar o método.Ainda é possível chamar métodos diretamente para o escolhido classe principal:

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

Virtualidade

Agora vamos adicionar herança virtual.Usando o mesmo exemplo de código com as seguintes alterações:

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

Permite saltar a criação da d:

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

Você pode ver, A é criado com o construtor padrão ignorando parâmetros passados a partir de construtores de B e C.Como a ambigüidade é ido, todas as chamadas para getX() retornar o mesmo valor:

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

Mas o que se deseja chamar parametrizando construtor para A?Isso pode ser feito através de uma chamada explícita a partir do construtor de D:

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

Normalmente, a classe pode usar explicitamente construtores direta dos pais, mas há uma exclusão de herança virtual caso.Descobrir a regra "clicado" para mim e ajudou-nos a compreender as interfaces virtuais muito:

Código class B: virtual A significa que qualquer classe herdada de B é agora responsável pela criação de A por si só, uma vez que B não vai fazer isso automaticamente.

Com esta afirmação em mente, é fácil responder a todas as perguntas que eu tinha:

  1. Durante D criação nem B nem C é responsável por parâmetros de A, é totalmente até D só.
  2. C irá delegar a criação de A para D, mas B vai criar sua própria instância do A trazendo diamante problema de volta
  3. A definição da base de dados de classe de parâmetros neto de classe, ao invés de incluir direto criança não é uma boa prática, por isso deve ser tolerado quando diamante problema existe e esta medida é inevitável.

O problema não é o caminho o compilador deve seguir. O problema é o endpoint Desse caminho: o resultado do elenco. Quando se trata de digitar conversões, o caminho não importa, apenas o resultado final o faz.

Se você usa a herança comum, cada caminho tem seu próprio endpoint distinto, o que significa que o resultado do elenco é ambíguo, que é o problema.

Se você usa a herança virtual, obtém uma hierarquia em forma de diamante: ambos os caminhos levam ao mesmo ponto de extremidade. Nesse caso, o problema de escolher o caminho não existe mais (ou, mais precisamente, não importa mais), porque ambos os caminhos levam ao mesmo resultado. O resultado não é mais ambíguo - é isso que importa. O caminho exato não.

Na verdade, o exemplo deve ser o seguinte:

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

... Dessa forma, a saída será a correta: "Coma => d"

A herança virtual resolve apenas a duplicação do avô! Mas você ainda precisa especificar os métodos para serem virtuais para obter os métodos corretamente substituídos ...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top