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() { /* ... */ }
};
c++
virtual-inheritance
popopome
fonte
fonte
Respostas:
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 a herança múltipla.
Considere o seguinte cenário:
A hierarquia de classes acima resulta no "diamante temido", que se parece com isso:
Uma instância de D será composta de B, que inclui A e C, que também inclui A. Portanto, você tem duas "instâncias" (por falta de uma melhor expressão) de A.
Quando você tem esse cenário, você tem a possibilidade de ambiguidade. O que acontece quando você faz isso:
Herança virtual existe para resolver esse problema. Ao especificar virtual ao herdar suas classes, você está dizendo ao compilador que deseja apenas uma única instância.
Isso significa que há apenas uma "instância" de A incluída na hierarquia. Conseqüentemente
Este é um mini resumo. Para mais informações, leia isto e isto . Um bom exemplo também está disponível aqui .
fonte
virtual
, o layout do objeto se parecerá com o diamante; e se não usarvirtual
, em seguida, os olhares de layout objeto como uma estrutura de árvore que contém doisA
sSobre o layout da memória
Como observação lateral, o problema com o Dreaded Diamond é que a classe base está presente várias vezes. Portanto, com herança regular, você acredita que possui:
Mas no layout da memória, você tem:
Isso explica por que, quando ligar
D::foo()
, você tem um problema de ambiguidade. Mas o verdadeiro problema surge quando você deseja usar uma variável membro deA
. Por exemplo, digamos que temos:Quando você tentar acessar
m_iValue
a partirD
, o compilador irá protestar, porque na hierarquia, vai ver doism_iValue
, não um. E se você modificar um, digamos,B::m_iValue
(que é oA::m_iValue
pai deB
),C::m_iValue
não será modificado (que é oA::m_iValue
pai deC
).É aqui que a herança virtual se torna útil; assim, você voltará a um verdadeiro layout de diamante, não apenas com um
foo()
método, mas também com um e apenas umm_iValue
.O que poderia dar errado?
Imagine:
A
tem alguma característica básica.B
adiciona a ele algum tipo de matriz legal de dados (por exemplo)C
adiciona a ele alguns recursos interessantes, como um padrão de observador (por exemplo, emm_iValue
).D
herda deB
eC
, e assim deA
.Com herança normal, a modificação
m_iValue
deD
é ambígua e isso deve ser resolvido. Mesmo que seja, existem doism_iValues
dentroD
, então é melhor você se lembrar disso e atualizar os dois ao mesmo tempo.Com herança virtual, modificar
m_iValue
deD
está ok ... Mas ... Digamos que você tenhaD
. Através de suaC
interface, você anexou um observador. E através de suaB
interface, você atualiza a matriz legal, que tem o efeito colateral de alterar diretamentem_iValue
...Como a alteração
m_iValue
é feita diretamente (sem usar um método de acessador virtual), o observador "ouvindo"C
não será chamado, porque o código que implementa a escuta está dentroC
eB
não sabe sobre isso ...Conclusão
Se você tem um diamante em sua hierarquia, significa que você tem 95% de probabilidade de ter feito algo errado com essa hierarquia.
fonte
Explicar a herança múltipla com bases virtuais requer um conhecimento do modelo de objeto C ++. E a explicação clara do tópico é melhor feita em um artigo e não em uma caixa de comentários.
A melhor e legível explicação que descobri que resolveu todas as minhas dúvidas sobre esse assunto foi este artigo: http://www.phpcompiler.org/articles/virtualinheritance.html
Você realmente não precisará ler mais nada sobre o assunto (a menos que seja um escritor de compilador) depois de ler isso ...
fonte
Eu acho que você está confundindo duas coisas muito diferentes. Herança virtual não é a mesma coisa que uma classe abstrata. A herança virtual modifica o comportamento das chamadas de função; às vezes, resolve chamadas de função que seriam ambíguas; às vezes, adia o tratamento de chamadas de função para uma classe diferente daquela que seria de esperar em uma herança não virtual.
fonte
Gostaria de acrescentar os esclarecimentos gentis de OJ.
A herança virtual não vem sem preço. Como em todas as coisas virtuais, você obtém um desempenho atingido. Existe uma maneira de contornar esse problema de desempenho que é possivelmente menos elegante.
Em vez de quebrar o diamante derivando virtualmente, você pode adicionar outra camada ao diamante, para obter algo assim:
Nenhuma das classes herda virtualmente, todas herdam publicamente. As classes D21 e D22 ocultarão a função virtual f (), que é ambígua para DD, talvez declarando a função privada. Cada um deles define uma função de wrapper, f1 () e f2 (), respectivamente, cada um chamando classe-local (privado) f (), resolvendo conflitos. A classe DD chama f1 () se desejar D11 :: f () e f2 () se desejar D12 :: f (). Se você definir os wrappers em linha, provavelmente terá uma sobrecarga zero.
Obviamente, se você pode alterar o D11 e o D12, pode fazer o mesmo truque nessas classes, mas geralmente esse não é o caso.
fonte
Além do que já foi dito sobre heranças múltiplas e virtuais, há um artigo muito interessante no Diário do Dr. Dobb: Herança Múltipla Considerada Útil
fonte
Você está sendo um pouco confuso. Não sei se você está misturando alguns conceitos.
Você não tem uma classe base virtual no seu OP. Você apenas tem uma classe base.
Você fez herança virtual. Isso geralmente é usado em herança múltipla, para que várias classes derivadas usem os membros da classe base sem reproduzi-los.
Uma classe base com uma função virtual pura não é instanciada. isso requer a sintaxe em que Paulo chega. É normalmente usado para que as classes derivadas definam essas funções.
Não quero explicar mais sobre isso porque não entendo totalmente o que você está perguntando.
fonte
Isso significa que uma chamada para uma função virtual será encaminhada para a classe "certa".
C ++ FAQ Lite FTW.
Em resumo, é frequentemente usado em cenários de herança múltipla, onde uma hierarquia de "diamantes" é formada. A herança virtual quebrará a ambiguidade 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 dessa classe inferior. Veja o item FAQ para um diagrama e detalhes.
Também é usado na delegação irmã , um recurso poderoso (embora não seja para os fracos de coração). Veja este FAQ.
Veja também o Item 40 na 3ª edição Efetiva do C ++ (43 na 2ª edição).
fonte
Exemplo de uso executável de herança de diamante
Este exemplo mostra como usar uma classe base virtual no cenário típico: para resolver a herança de diamantes.
fonte
assert(A::aDefault == 0);
da função principal me dá um erro de compilação:aDefault is not a member of A
usando o gcc 5.4.0. O que é suposto fazer?Classes virtuais não são iguais a herança virtual. Classes virtuais que você não pode instanciar; herança virtual é algo totalmente diferente.
A Wikipedia descreve melhor do que eu. http://en.wikipedia.org/wiki/Virtual_inheritance
fonte
Herança regular
Com a herança típica de herança não virtual de três níveis, sem diamante, quando você instancia um novo objeto mais derivado, new é chamado e o tamanho necessário para o objeto é resolvido do tipo de classe pelo compilador e passado para novo.
new tem uma assinatura:
E faz uma chamada para
malloc
, retornando o ponteiro nuloIsso é passado ao construtor do objeto mais derivado, que chamará imediatamente o construtor intermediário e, em seguida, o construtor intermediário chamará imediatamente o construtor base. A base então armazena um ponteiro para sua tabela virtual no início do objeto e, em seguida, seus atributos após ele. Isso retorna ao construtor do meio que armazenará seu ponteiro de tabela virtual no mesmo local e, em seguida, seus atributos após os atributos que teriam sido armazenados pelo construtor de base. Ele retorna ao construtor mais derivado, que armazena um ponteiro para sua tabela virtual no mesmo local e, em seguida, seus atributos após os atributos que teriam sido armazenados pelo construtor intermediário.
Como o ponteiro da tabela virtual é substituído, o ponteiro da tabela virtual acaba sempre sendo o da classe mais derivada. A virtualidade se propaga para a classe mais derivada; portanto, se uma função for virtual na classe média, ela será virtual na classe mais derivada, mas não na classe base. Se você polimorficamente converter uma instância da classe mais derivada em um ponteiro para a classe base, o compilador não resolverá isso para uma chamada indireta para a tabela virtual e, em vez disso, chamará a função diretamente
A::function()
. Se uma função é virtual para o tipo para o qual você a lançou, ela será resolvida para uma chamada na tabela virtual, que sempre será a da classe mais derivada. Se não for virtual para esse tipo, ele simplesmente chamaráType::function()
e passará o ponteiro do objeto para ele, convertido em Type.Na verdade, quando digo ponteiro para sua tabela virtual, na verdade é sempre um deslocamento de 16 na tabela virtual.
virtual
não é necessário novamente em classes mais derivadas se for virtual em uma classe menos derivada porque se propaga. Mas pode ser usado para mostrar que a função é realmente uma função virtual, sem precisar verificar as classes que herda as definições de tipo.override
é outro guarda do compilador que diz que esta função está substituindo algo e, se não estiver, gera um erro do compilador.= 0
significa que esta é uma função abstratafinal
impede que uma função virtual seja implementada novamente em uma classe mais derivada e assegura que a tabela virtual da classe mais derivada contenha a função final dessa classe.= default
torna explícito na documentação que o compilador usará a implementação padrão= delete
dê um erro de compilador se uma chamada para isso for tentadaHerança virtual
Considerar
Sem virtualmente herdar a classe de baixo, você obterá um objeto parecido com este:
Em vez disso:
Ou seja, haverá 2 objetos de base.
Na situação herança diamante virtuais acima, após novo é chamado, ele chama o construtor mais derivado e nesse construtor, ele chama todos os 3 construtores derivados passando compensações em sua tabela de mesa virtual, em vez de chamar apenas chamando
DerivedClass1::DerivedClass1()
eDerivedClass2::DerivedClass2()
em seguida os dois chamadaBase::Base()
O seguinte é todo compilado no modo de depuração -O0, para que haja um assembly redundante
Ele chama
Base::Base()
com um ponteiro para o deslocamento 32 do objeto. A base armazena um ponteiro para sua tabela virtual no endereço que ele recebe e seus membros depois dele.DerivedDerivedClass::DerivedDerivedClass()
depois chamaDerivedClass1::DerivedClass1()
com um ponteiro para o deslocamento 0 do objeto e também passa o endereço deVTT for DerivedDerivedClass+8
DerivedDerivedClass::DerivedDerivedClass()
depois passa o endereço do objeto + 16 e o endereço da VTT paraDerivedDerivedClass+24
paraDerivedClass2::DerivedClass2()
cuja montagem é idêntica,DerivedClass1::DerivedClass1()
exceto para a linhamov DWORD PTR [rax+8], 3
que obviamente possui 4 em vez de 3 parad = 4
.Depois disso, ele substitui todos os três ponteiros de tabela virtual no objeto por ponteiros para compensar a tabela de
DerivedDerivedClass
v na representação da classe.d->VirtualFunction();
:d->DerivedCommonFunction();
:d->DerivedCommonFunction2();
:d->DerivedDerivedCommonFunction();
:((DerivedClass2*)d)->DerivedCommonFunction2();
:((Base*)d)->VirtualFunction();
:fonte