Herança virtual - Virtual inheritance
Herança virtual é um C ++ técnica que assegura que apenas uma cópia de uma classe base ' s membro variáveis são herdadas por classes neto derivado. Sem herança virtual, se duas classes B
e C
herdam de uma classe A
e uma classe D
herda de ambos B
e C
, em seguida, D
irá conter duas cópias de A
' variáveis de membro s: uma via B
, e uma via C
. Eles estarão acessíveis de forma independente, usando resolução de escopo .
Em vez disso, se as classes B
e C
herdam virtualmente da classe A
, os objetos da classe D
conterão apenas um conjunto de variáveis de membro da classe A
.
Esse recurso é mais útil para herança múltipla , pois torna a base virtual um subobjeto comum para a classe derivada e todas as classes derivadas dela. Isso pode ser usado para evitar o problema do diamante , esclarecendo a ambigüidade sobre qual classe ancestral usar, pois da perspectiva da classe derivada ( D
no exemplo acima) a base virtual ( A
) age como se fosse a classe base direta de D
, não uma classe derivada indiretamente por meio de uma base ( B
ou C
).
É usado quando a herança representa a restrição de um conjunto em vez da composição das partes. Em C ++, uma classe base que pretende ser comum em toda a hierarquia é denotada como virtual com a virtual
palavra - chave .
Considere a seguinte hierarquia de classes.
struct Animal {
virtual ~Animal() = default;
virtual void Eat() {}
};
struct Mammal: Animal {
virtual void Breathe() {}
};
struct WingedAnimal: Animal {
virtual void Flap() {}
};
// A bat is a winged mammal
struct Bat: Mammal, WingedAnimal {};
Bat bat;
Conforme declarado acima, uma chamada para bat.Eat
é ambígua porque há duas Animal
classes base (indiretas) em Bat
, portanto, qualquer Bat
objeto tem dois Animal
subobjetos de classe base diferentes . Portanto, uma tentativa de vincular diretamente uma referência ao Animal
subobjeto de um Bat
objeto falharia, uma vez que a vinculação é inerentemente ambígua:
Bat b;
Animal& a = b; // error: which Animal subobject should a Bat cast into,
// a Mammal::Animal or a WingedAnimal::Animal?
Para eliminar a ambigüidade, seria necessário converter explicitamente bat
para qualquer um dos subobjetos da classe base:
Bat b;
Animal& mammal = static_cast<Mammal&>(b);
Animal& winged = static_cast<WingedAnimal&>(b);
Para chamar Eat
, a mesma desambiguação ou qualificação explícita é necessária: static_cast<Mammal&>(bat).Eat()
ou static_cast<WingedAnimal&>(bat).Eat()
ou alternativamente bat.Mammal::Eat()
e bat.WingedAnimal::Eat()
. A qualificação explícita não apenas usa uma sintaxe uniforme e mais fácil para ponteiros e objetos, mas também permite o envio estático, portanto, seria o método preferível.
Nesse caso, a herança dupla de Animal
é provavelmente indesejada, pois queremos modelar que a relação ( Bat
é uma Animal
) existe apenas uma vez; que a Bat
é a Mammal
e é a WingedAnimal
não implica que seja Animal
duas vezes: uma Animal
classe base corresponde a um contrato que Bat
implementa (o relacionamento "é um" acima realmente significa " implementa os requisitos de "), e a Bat
apenas implementa o Animal
contrato uma vez . O significado do mundo real de " é apenas uma vez" é que Bat
deve haver apenas uma forma de implementação Eat
, não duas formas diferentes, dependendo se a Mammal
visão do Bat
está comendo ou a WingedAnimal
visão do Bat
. (No primeiro exemplo de código, vemos que Eat
não é substituído em Mammal
ou WingedAnimal
, portanto, os dois Animal
subobjetos realmente se comportarão da mesma forma, mas este é apenas um caso degenerado e isso não faz diferença do ponto de vista do C ++.)
Essa situação às vezes é chamada de herança de diamante (consulte o problema do diamante ) porque o diagrama de herança tem a forma de um diamante. A herança virtual pode ajudar a resolver esse problema.
A solução
Podemos declarar novamente nossas classes da seguinte maneira:
struct Animal {
virtual ~Animal() = default;
virtual void Eat() {}
};
// Two classes virtually inheriting Animal:
struct Mammal: virtual Animal {
virtual void Breathe() {}
};
struct WingedAnimal: virtual Animal {
virtual void Flap() {}
};
// A bat is still a winged mammal
struct Bat: Mammal, WingedAnimal {};
A Animal
parte de Bat::WingedAnimal
é agora a mesma Animal
instância que a usada por Bat::Mammal
, o que significa que a Bat
tem apenas uma Animal
instância compartilhada em sua representação e, portanto, uma chamada para Bat::Eat
não é ambígua. Além disso, uma conversão direta de Bat
para Animal
também não é ambígua, agora que existe apenas uma Animal
instância para a qual Bat
poderia ser convertida.
A capacidade de compartilhar uma única instância do Animal
pai entre Mammal
e WingedAnimal
é ativada pelo registro do deslocamento de memória entre os membros Mammal
ou WingedAnimal
e aqueles da base Animal
dentro da classe derivada. No entanto, este pode compensar no caso geral só é conhecido em tempo de execução, portanto, Bat
deve tornar-se ( vpointer
, Mammal
, vpointer
, WingedAnimal
, Bat
, Animal
). Existem dois ponteiros vtable , um por hierarquia de herança que virtualmente herda Animal
. Neste exemplo, um para Mammal
e um para WingedAnimal
. O tamanho do objeto, portanto, aumentou em dois ponteiros, mas agora há apenas um Animal
e nenhuma ambigüidade. Todos os objetos do tipo Bat
usarão os mesmos vpointers, mas cada Bat
objeto conterá seu próprio Animal
objeto exclusivo . Se outra classe herda de Mammal
, como Squirrel
, então o vpointer na Mammal
parte de Squirrel
geralmente será diferente do vpointer na Mammal
parte de, Bat
embora possam acontecer ser os mesmos se a Squirrel
classe tiver o mesmo tamanho que Bat
.
Exemplo Adicional
Este exemplo ilustra um caso em que a classe base A
tem uma variável de construtor msg
e um ancestral adicional E
é derivado da classe neto D
.
A / \ B C \ / D | E
Aqui, A
deve ser construído em ambos D
e E
. Além disso, a inspeção da variável msg
ilustra como a classe A
se torna uma classe base direta de sua classe derivada, em oposição a uma classe base de qualquer classe derivada intermediária A
e a classe derivada final. O código abaixo pode ser explorado interativamente aqui .
#include <string>
#include <iostream>
class A {
private:
std::string _msg;
public:
A(std::string x): _msg(x) {}
void test(){ std::cout<<"hello from A: "<<_msg <<"\n"; }
};
// B,C inherit A virtually
class B: virtual public A { public: B(std::string x):A("b"){} };
class C: virtual public A { public: C(std::string x):A("c"){} };
// since B,C inherit A virtually, A must be constructed in each child
class D: public B,C { public: D(std::string x):A("d_a"),B("d_b"),C("d_c"){} };
class E: public D { public: E(std::string x):A("e_a"),D("e_d"){} };
// breaks without constructing A
// class D: public B,C { public: D(std::string x):B(x),C(x){} };
// breaks without constructing A
//class E: public D { public: E(std::string x):D(x){} };
int main(int argc, char ** argv){
D d("d");
d.test(); // hello from A: d_a
E e("e");
e.test(); // hello from A: e_a
}