vínculo - Em C++, o que é uma classe base virtual?




funções virtuais são resolvidas usando se vínculo dinâmico (7)

Eu quero saber o que é uma " classe base virtual " e o que isso significa.

Deixe-me mostrar um exemplo:

class Foo
{
public:
    void DoSomething() { /* ... */ }
};

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};

Uma classe base virtual é uma classe que não pode ser instanciada: você não pode criar um objeto direto a partir dela.

Eu acho que você está confundindo duas coisas muito diferentes. Herança virtual não é a mesma coisa que uma classe abstrata. Herança virtual modifica o comportamento de chamadas de função; às vezes, ele resolve chamadas de função que, de outra forma, seriam ambíguas, às vezes, adia a manipulação de chamadas de função para uma classe diferente daquela que seria esperada em uma herança não virtual.


Sobre o layout da memória

Como uma nota lateral, o problema com o Dreaded Diamond é que a classe base está presente várias vezes. Então, com herança regular, você acredita ter:

  A
 / \
B   C
 \ /
  D

Mas no layout da memória, você tem:

A   A
|   |
B   C
 \ /
  D

Isso explica porque quando você chama D::foo() , você tem um problema de ambigüidade. Mas o problema real vem quando você quer usar uma variável de membro de A Por exemplo, digamos que temos:

class A
{
    public :
       foo() ;
       int m_iValue ;
} ;

Quando você tentar acessar m_iValue partir de D , o compilador protestará, porque na hierarquia, verá dois m_iValue , não um. E se você modificar um, digamos, B::m_iValue (que é o pai A::m_iValue de B ), C::m_iValue não será modificado (que é o pai A::m_iValue de C ).

É aqui que a herança virtual é útil, pois, com ela, você retornará a um verdadeiro layout de losango, com não apenas um método foo() , mas também um e apenas um m_iValue .

O que poderia dar errado?

Imagine:

  • A tem algum recurso básico.
  • B adiciona algum tipo de matriz legal de dados (por exemplo)
  • C adiciona algum recurso interessante como um padrão de observador (por exemplo, em m_iValue ).
  • D herda de B e C e, portanto, de A

Com herança normal, modificar m_iValue de D é ambíguo e isso deve ser resolvido. Mesmo que seja, há dois m_iValues dentro de D , então é melhor lembrar disso e atualizar os dois ao mesmo tempo.

Com herança virtual, modificar m_iValue de D é ok ... Mas ... Digamos que você tenha D Através de sua interface C , você conectou um observador. E através da sua interface B , você atualiza o array cool, que tem o efeito colateral de alterar diretamente o m_iValue ...

Como a mudança de m_iValue é feita diretamente (sem usar um método de acesso virtual), o observador "escutando" através de C não será chamado, porque o código que implementa a escuta está em C , e B não o conhece. .

Conclusão

Se você está tendo um diamante na sua hierarquia, isso significa que você tem 95% para fazer algo errado com a dita hierarquia.


As classes base virtuais, usadas na herança virtual, são uma maneira de impedir que várias "instâncias" de uma determinada classe apareçam em uma hierarquia de herança ao usar herança múltipla.

Considere o seguinte cenário:

class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};

A hierarquia de classes acima resulta no "diamante temido", que se parece com isso:

  A
 / \
B   C
 \ /
  D

Uma instância de D será composta de B, que inclui A e C, que também inclui A. Então você tem duas "instâncias" (por falta de uma expressão melhor) de A.

Quando você tem esse cenário, você tem a possibilidade de ambigüidade. O que acontece quando você faz isso:

D d;
d.Foo(); // is this B's Foo() or C's Foo() ??

Herança virtual está lá para resolver esse problema. Quando você especifica virtual ao herdar suas classes, você está dizendo ao compilador que você quer apenas uma única instância.

class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};

Isso significa que há apenas uma "instância" de A incluída na hierarquia. Conseqüentemente

D d;
d.Foo(); // no longer ambiguous

Espero que ajude como um mini resumo. Para mais informações, leia this e this . Um bom exemplo também está disponível here .


Classes virtuais não são o mesmo que herança virtual. Classes virtuais que você não pode instanciar, herança virtual é algo completamente diferente.

Wikipedia descreve melhor do que eu. this


Explicar herança múltipla com bases virtuais requer um conhecimento do modelo de objeto C ++. E explicar o tópico claramente é melhor feito em um artigo e não em uma caixa de comentários.

A melhor explicação legível que encontrei para resolver todas as minhas dúvidas sobre este assunto foi este artigo: http://www.phpcompiler.org/articles/virtualinheritance.html

Você realmente não precisa ler mais nada sobre o tópico (a menos que você seja um escritor de compilação) depois de ler isso ...


Isso significa que uma chamada para uma função virtual será encaminhada para a classe "certa".

this C ++ this FTW.

Em suma, é frequentemente usado em cenários de herança múltipla, onde uma hierarquia "diamante" é formada. A herança virtual irá então quebrar a ambigüidade criada na classe inferior, quando você chamar a função nessa classe e a função precisar ser resolvida para a classe D1 ou D2 acima daquela classe inferior. Veja o item da FAQ para um diagrama e detalhes.

Também é usado em delegação irmã , um recurso poderoso (embora não para cardíacos). Veja this FAQ.

Veja também o item 40 em C ++ 3ª edição efetiva (43 na 2ª edição).


Exemplo de uso executável de herança de diamantes

Este exemplo mostra como usar uma classe base virtual no cenário típico: para resolver a herança de diamantes.

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




virtual-inheritance