Várias classes base
Uma classe pode ser derivada de mais de uma classe base. Em um modelo de várias heranças (no qual as classes são derivadas de mais de uma classe base), as classes base são especificadas usando o elemento de gramática lista base. Por exemplo, a declaração de classe de CollectionOfBook
, derivada de Collection
e de Book
, pode ser especificada:
// deriv_MultipleBaseClasses.cpp
// compile with: /LD
class Collection {
};
class Book {};
class CollectionOfBook : public Book, public Collection {
// New members
};
A ordem em que as classes base são especificadas não é significativa, exceto em certos casos em que construtores e destruidores são chamados. Nesses casos, a ordem na qual as classes base são especificadas afeta o seguinte:
A ordem em que os construtores são chamados. Se seu código depende da parte
Book
deCollectionOfBook
para ser inicializado antes da parteCollection
, a ordem de especificação é significante. A inicialização ocorre na ordem em que as classes são especificadas na lista base.A ordem na qual os destruidores são chamados para limpeza. Novamente, se for necessário que uma "parte" específica da classe esteja presente quando outra parte for destruída, a ordem é importante. Os destruidores são chamados na ordem inversa das classes especificadas na lista base.
Observação
A ordem de especificação das classes base pode afetar o layout de memória da classe. Não tome decisões de programação com base na ordem dos membros base na memória.
Ao especificar a lista base, você não pode especificar o mesmo nome de classe mais de uma vez. No entanto, é possível que uma classe seja uma base indireta para uma classe derivada mais de uma vez.
Classes base virtuais
Porque uma classe pode ser uma classe base indireta a uma classe derivada mais de uma vez, C++ fornece uma maneira de otimizar a forma como funcionam as classes base. As classes base virtuais oferecem uma maneira de economizar espaço e evitar ambiguidades nas hierarquias de classes que usam a herança múltipla.
Cada objeto não virtual contém uma cópia dos membros de dados definidos na classe base. Essa duplicação perde espaço e exige que você especifique que cópia dos membros da classe base você quer sempre que os acessa.
Quando uma classe base é especificada como base virtual, ela pode atuar como uma base indireta mais de uma vez sem duplicação de seus membros de dados. Uma única cópia dos membros de dados é compartilhada por todas as classes base que ela usa como base virtual.
Ao declarar uma classe base virtual, a palavra-chave virtual
aparece nas listas de base das classes derivadas.
Considere a hierarquia de classe na figura a seguir, que ilustra uma linha de almoço simulada:
A classe base é Queue. A Fila do Caixa e a Fila do Almoço herdam da Fila. Por fim, a Fila do Caixa do Almoço herda tanto da Fila do Caixa quanto da Fila do Almoço.
Gráfico simulado da linha de almoço
Na figura, Queue
é a classe base para CashierQueue
e LunchQueue
. No entanto, quando as duas classes são combinadas para formar LunchCashierQueue
, o seguinte problema ocorre: a nova classe contém dois subobjetos do tipo Queue
, um de CashierQueue
e o outro de LunchQueue
. A figura a seguir mostra o layout de memória conceitual (o layout de memória real pode ser otimizado):
A figura mostra um objeto Lunch Cashier Queue com dois subobjetos: Fila de Caixa e Fila de Almoço. Tanto a Fila de Caixa quanto a Fila de Almoço contêm um subobjeto Fila."
Objeto de linha de almoço simulado
Há dois Queue
subobjetos no LunchCashierQueue
objeto. O código a seguir declara Queue
como uma classe base virtual:
// deriv_VirtualBaseClasses.cpp
// compile with: /LD
class Queue {};
class CashierQueue : virtual public Queue {};
class LunchQueue : virtual public Queue {};
class LunchCashierQueue : public LunchQueue, public CashierQueue {};
A palavra-chave virtual
garante que apenas uma cópia do subobjeto Queue
seja incluída (veja a figura a seguir).
O diagrama mostra um objeto Lunch Cashier Queue, que contém um subobject Fila de Caixa e um subobjeto Lunch Queue. Tanto a Fila de Caixa quanto a Fila de Almoço compartilham o mesmo subobjeto Fila.
Objeto de linha de almoço simulado com classes base virtuais
Uma classe pode ter um componente virtual e um componente não virtual de determinado tipo. Isso acontece nas condições ilustradas na figura a seguir:
O diagrama mostra uma classe base de fila. Uma classe Fila de Caixa e uma classe Fila de Almoço herdam virtualmente de Fila. Uma terceira classe, Takeout Queue, herda não virtualmente da fila. A Fila do Caixa do Almoço herda da Fila do Caixa e da Fila do Almoço. A Fila do Caixa do Almoço para Levar herda da Fila do Caixa do Almoço e da Fila do Takeout.
Componentes virtuais e não virtuais da mesma classe
Na figura, CashierQueue
e LunchQueue
usam Queue
como uma classe base virtual. No entanto, TakeoutQueue
especifica Queue
como uma classe base, não uma classe base virtual. Portanto, LunchTakeoutCashierQueue
tem dois subobjetos do tipo Queue
: um do caminho de herança que inclui LunchCashierQueue
e outro do caminho que inclui TakeoutQueue
. Isso é ilustrado na figura a seguir.
Um objeto Lunch Takeout Cashier Queue é mostrado que contém dois subobjetos: uma Fila de Takeout (que contém um subobjeto Queue) e uma Lunch Cashier Queue. O subobjeto Fila de Caixa de Almoço contém um subobjeto Fila de Caixa e um subobjeto Fila de Almoço, que compartilham um subobjeto Fila.
Layout de objeto com herança virtual e não virtual
Observação
A herança virtual oferece benefícios significativos de tamanho quando comparada com a herança não virtual. No entanto, pode apresentar a sobrecarga adicional de processamento.
Se uma classe derivada substitui uma função virtual que ela herda de uma classe base virtual, e se um construtor ou um destruidor para a classe base derivada chama essa função usando um ponteiro para a classe base virtual, o compilador pode introduzir outros campos "vtordisp" ocultos nas classes com bases virtuais. A opção do compilador /vd0
suprime a adição do membro oculto de deslocamento do construtor/destruidor vtordisp. A /vd1
opção do compilador, o padrão, os habilita onde são necessários. Desative vtordisps somente se tiver certeza de que todos os construtores e destruidores de classe chamam funções virtuais virtualmente.
A opção do compilador /vd
afeta um módulo de compilação inteiro. Use o pragma vtordisp
para suprimir e habilitar novamente os campos vtordisp
classe por classe:
#pragma vtordisp( off )
class GetReal : virtual public { ... };
\#pragma vtordisp( on )
Ambiguidades de nome
A herança múltipla apresenta a possibilidade de que os nomes sejam herdados ao longo de mais de um caminho. Os nomes dos membros da classe ao longo desses caminhos não são necessariamente exclusivos. Esses conflitos de nome são chamados de “ambiguidades”.
Qualquer expressão que se referir a um membro de classe deve fazer uma referência não ambígua. O exemplo a seguir mostra como as ambiguidades se desenvolvem:
// deriv_NameAmbiguities.cpp
// compile with: /LD
// Declare two base classes, A and B.
class A {
public:
unsigned a;
unsigned b();
};
class B {
public:
unsigned a(); // class A also has a member "a"
int b(); // and a member "b".
char c;
};
// Define class C as derived from A and B.
class C : public A, public B {};
Dadas as declarações de classe anteriores, o código como o seguinte é ambíguo porque não está claro se se b
refere ao b
in ou in B
A
:
C *pc = new C;
pc->b();
Considere o exemplo anterior. Como o nome a
é um membro da classe e da classeB
A
, o compilador não pode discernir qual a
designa a função a ser chamada. O acesso a um membro é ambíguo se pode referenciar mais de uma função, objeto, tipo ou enumerador.
O compilador detecta ambiguidades executando testes nesta ordem:
Se o acesso ao nome é ambíguo (como somente descrito), será gerada uma mensagem de erro.
Se as funções sobrecarregadas forem inequívocas, elas serão resolvidas.
Se o acesso ao nome viola a permissão de acesso a membros, será gerada uma mensagem de erro. (Para mais informações, confira Controle de acesso a membros.)
Quando uma expressão gera uma ambiguidade por meio de uma herança, você pode solucioná-la manualmente qualificando o nome em questão com seu nome de classe. Para fazer com que o exemplo acima seja compilado corretamente sem ambiguidades, use códigos como:
C *pc = new C;
pc->B::a();
Observação
Quando C
é declarado, ele tem o potencial para causar erros quando B
é referenciado no escopo de C
. Nenhum erro é emitido, no entanto, até que uma referência não qualificada a B
seja feita no escopo de C
.
Dominância
É possível que mais de um nome (função, objeto ou enumerador) seja alcançado por meio de um gráfico de herança. Esses casos são considerados ambíguos com classes base não virtuais. Eles também são ambíguos com as classes base virtuais, a menos que um dos nomes "domine" os outros.
Um nome domina outro nome se for definido em ambas as classes e uma classe for derivada da outra. O nome dominante é o nome na classe derivada; esse nome é usado quando uma ambiguidade poderia surgir de outra forma, conforme mostrado no seguinte exemplo:
// deriv_Dominance.cpp
// compile with: /LD
class A {
public:
int a;
};
class B : public virtual A {
public:
int a();
};
class C : public virtual A {};
class D : public B, public C {
public:
D() { a(); } // Not ambiguous. B::a() dominates A::a.
};
Conversões ambíguas
As conversões explícitas e implícitas de ponteiros ou as referências aos tipos da classe podem causar ambiguidades. A figura a seguir, a conversão ambígua dos ponteiros para as classes base, mostra o seguinte:
A declaração de um objeto de tipo
D
.O efeito da aplicação do endereço do operador (&) a esse objeto. O operador de endereço sempre fornece o endereço base do objeto.
O efeito de converter explicitamente o ponteiro obtido usando o operador address-of para o tipo de classe base
A
. Coagir o endereço do objeto a ser digitadoA*
nem sempre fornece ao compilador informações suficientes sobre qual subobjeto de tipoA
selecionar, nesse caso, existem dois subobjetos.
O diagrama primeiro mostra uma hierarquia de herança: A é a classe base. B e C herdam de A. D herda de B e C. Em seguida, o layout de memória é mostrado para o objeto D. Há três subobjetos em D: B (que inclui um subobjeto A) e C (que inclui um subobjeto A). O código & d aponta para o A no subobjeto B. O código ( * A ) & d aponta para o subobjeto B e o subobjeto C.
Conversão ambígua de ponteiros em classes base
A conversão para tipo (ponteiro para A
) é ambígua porque não há como discernir qual subobjeto de tipo A*
A
é o correto. Você pode evitar a ambiguidade especificando explicitamente qual subobjeto pretende usar, da seguinte maneira:
(A *)(B *)&d // Use B subobject.
(A *)(C *)&d // Use C subobject.
Ambiguidades e classes base virtuais
Se forem usadas classes base virtuais, as funções, os objetos, os tipos e os enumeradores poderão ser alcançados por meio de caminhos de herança múltipla. Como há apenas uma instância da classe base, não há ambiguidade ao acessar esses nomes.
A figura a seguir mostra como os objetos são compostos usando a herança virtual e não virtual.
O diagrama primeiro mostra uma hierarquia de herança: A é a classe base. B e C praticamente herdam de A. D virtualmente herda de B e C. Em seguida, o layout de D é mostrado. D contém os subobjetos B e C, que compartilham o subobjeto A. Em seguida, o layout é mostrado como se a mesma hierarquia tivesse sido derivada usando herança não virtual. Nesse caso, D contém os subobjetos B e C. Tanto B quanto C contêm sua própria cópia do subobjeto A.
Derivação virtual e não virtual
Na figura, o acesso a qualquer membro da classe A
por meio de classes base não virtuais causa uma ambiguidade; o compilador não tem nenhuma informação que explique se ele deve usar o subobjeto associado a B
ou o subobjeto associado a C
. No entanto, quando A
é especificado como uma classe base virtual, não há dúvida de qual subobjeto está sendo acessado.
Confira também
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de