Tabelas virtuais e apontadores virtuais para vários herança virtual e o tipo de carcaça
-
28-09-2020 - |
Pergunta
Eu estou pouco confuso sobre vptr e representação de objetos na memória, e espero que você possa me ajudar a entender o assunto, melhor.
Considere
B
herdaA
e ambos definem funções virtuaisf()
.Desde que eu aprendi a representação de um objeto da classe B, em que a memória se parece com isso:[ vptr | A | B ]
e ovtbl
quevptr
pontos a contémB::f()
.Também percebi que o elenco do objeto daB
paraA
não faz nada, exceto ignorando oB
parte no final do objeto.É verdade?Não que esse comportamento é errado?Queremos que o objeto do tipoA
para executarA::f()
método e nãoB::f()
.Há um número de
vtables
no sistema, como o número de aulas?Como é que um
vtable
de classe que herda de duas ou mais classes de aparência?Como o objeto de C ser representados na memória?Mesmo que a pergunta 3, mas com a herança virtual.
Solução
O seguinte é verdadeiro para o GCC (e isso parece ser verdade para o LLVM link), mas também pode ser verdadeiro para o compilador que você estiver usando.Todos estes é dependente de implementação, e não é regida pelo padrão de C++.No entanto, o GCC gravar seus próprios binários padrão de documento, Itanium ABI.
Eu tentei explicar conceitos básicos de como tabelas virtuais são estabelecidas em mais palavras simples, como uma parte da minha artigo sobre a função virtual desempenho em C++, o que você pode achar úteis.Aqui estão as respostas para suas perguntas:
Uma forma mais correcta para descrever representação interna do objeto é:
| vptr | ======= | ======= | <-- your object |----A----| | |---------B---------|
B
contém sua classe baseA
, ele apenas acrescenta alguns de seus próprios membros, e após o seu fim.Fundição de
B*
paraA*
de fato, não faz nada, ele retorna o mesmo ponteiro, evptr
permanece o mesmo.Mas, em poucas palavras, funções virtuais não são sempre chamados através da vtable.Às vezes, eles são chamados assim como as outras funções.Veja explicação mais detalhada.Você deve distinguir duas formas de chamar a função de membro:
A a, *aptr; a.func(); // the call to A::func() is precompiled! aptr->A::func(); // ditto aptr->func(); // calls virtual function through vtable. // It may be a call to A::func() or B::func().
A única coisa é que ele é conhecido em tempo de compilação como a função será chamada:através da vtable ou apenas vai ser um costume de chamar.E a coisa é que o tipo de uma carcaça de expressão é conhecida em tempo de compilação, e , portanto, o compilador escolhe a função direita em tempo de compilação.
B b, *bptr; static_cast<A>(b)::func(); //calls A::func, because the type // of static_cast<A>(b) is A!
Ele nem mesmo olhar para dentro de vtable neste caso!
Geralmente, não.Uma classe pode ter várias vtables se herda várias bases, cada uma com sua própria vtable.Tal conjunto de tabelas virtuais, formulários de uma "mesa virtual de grupo" (cf.3).
Classe precisa também de um conjunto de construção vtables, corretamente distpatch funções virtuais ao construir as bases de um objeto complexo.Você pode ler mais em o padrão I vinculado.
Aqui está um exemplo.Suponha
C
herdaA
eB
, cada definição de classevirtual void func()
, assim comoa
,b
ouc
função virtual relevantes para o seu nome.O
C
vai ter uma vtable grupo de dois vtables.Ele irá compartilhar uma vtable comA
(vtable onde as próprias funções da atual classe de ir é chamada de "primária"), e uma vtable paraB
vai ser acrescentados:| C::func() | a() | c() || C::func() | b() | |---- vtable for A ----| |---- vtable for B ----| |--- "primary virtual table" --||- "secondary vtable" -| |-------------- virtual table group for C -------------|
A representação do objeto na memória vai olhar quase da mesma maneira a sua vtable parece.Basta adicionar um
vptr
antes de cada vtable em um grupo, e você vai ter uma estimativa de como os dados são dispostos dentro do objeto.Você pode ler mais sobre isto no seção relevante do GCC binário padrão.Virtual bases (alguns deles) são estabelecidas no final da vtable grupo.Isso é feito porque cada classe deve ter apenas uma base virtual, e se eles tombaram "usual" vtables, em seguida, o compilador não podia voltar a usar partes de construído vtables para fazer com que aqueles de classes derivadas.Isto poderia levar a computação desnecessários e deslocamentos poderá diminuir o desempenho.
Devido a tal posicionamento, o virtual bases de introduzir, nas suas vtables elementos adicionais:
vcall
o deslocamento (para obter o endereço de uma final overrider ao saltar do ponteiro para uma base virtual dentro de um objeto completo para o início da classe que substitui a função virtual) para cada função virtual definidos.Também cada base virtual adicionavbase
deslocamentos, que são inseridos vtable da classe derivada;eles permitem encontrar o local onde os dados de base virtual de começar (não pode ser pré-compilados desde o endereço real depende de hierarquia:virtual bases estão no final do objeto, e a mudança de início varia, dependendo de como muitos não-aulas virtuais, a atual classe herda.).
A trama, espero que eu não introduzir muito complexidade desnecessária.Em qualquer caso, você pode consultar o padrão original, ou de qualquer documento de seu próprio compilador.
Outras dicas
- O que parece correto para mim.Não é errado como se você estiver usando Um ponteiro, você só precisa que fornecem mais, talvez, B funções de implementações que estão disponíveis a partir de Uma vtable (pode haver vários vtable, dependendo do compilador e hierarquia de complexidade).
- Eu diria que sim, mas é a implementação de compilador dependente, então você realmente não tem que saber sobre ele.
- e 4.Leia mais longe.
Eu recomendaria a leitura Herança Múltipla Consideradas Úteis , é um artigo longo, mas ele torna as coisas mais claras sobre o assunto como ele explica em detalhes como a herança funciona em C++ (figuras links não funcionam, mas eles estão disponíveis na parte inferior da página).
Se o objeto B herda de A, então a memória de representação para B será o seguinte:
- ponteiro para a tabela virtual de Um
- Um determinado variáveis/funções
- ponteiro para a tabela virtual de B
- B determinadas variáveis/funções/substituições
Se você tem B* b = new B();(A)b->f (), em seguida:
- se f foi declarada como uma função virtual, em seguida, a B a implementação é chamada porque b é do tipo B
- se f não foi declarada como uma função virtual, em seguida, quando chamado, não haverá de pesquisa em que ele vtable para a correta implementação e A execução vai ser chamado.
Cada objeto terá seu próprio vtable (não tome isso como certo, como eu tenho para investigação
Dê uma olhada no este para um exemplo de vtable layour quando lidando com herança múltipla
Ver este para uma discussão sobre o diamante de herança e a vtable representação