Existem casos em que uma classe declara métodos virtuais e o compilador não precisa usar um vptr?

StackOverflow https://stackoverflow.com/questions/2336107

  •  22-09-2019
  •  | 
  •  

Pergunta

Fiquei me perguntando se existe uma possível otimização onde o compilador não precisa atribuir um vptr a um objeto instanciado, mesmo que o tipo do objeto seja uma classe com métodos virtuais.

Por exemplo, considere:

#include <iostream>
struct FooBase
{
  virtual void bar()=0;
};

struct FooDerived : public FooBase
{
  virtual void bar() { std::cout << "FooDerived::bar()\n"; }
};

int main()
{
   FooBase* pFoo = new FooDerived();
   pFoo->bar();

  return 0;
}

Neste exemplo o compilador com certeza sabe qual será o tipo de pFoo em tempo de compilação, então não precisa usar um vptr para pFoo, certo?Existem casos mais interessantes em que o compilador pode evitar o uso de um vptr?

Foi útil?

Solução

Construindo em Resposta de Andrew Stein, porque acho que você também quer saber quando a chamada "tendência de funções virtuais" pode ser evitada. (A sobrecarga está lá, mas é minúsculo, e raramente vale a pena se preocupar.)

É realmente difícil evitar o espaço do ponteiro vtable, mas o ponteiro em si pode ser ignorado, inclusive no seu exemplo. Porque pfooA inicialização está nesse escopo, o compilador sabe que pFoo->bar deve significar Fooderived :: Bar, e não precisa verificar o vtable. Existem também várias técnicas de cache para evitar várias pesquisas VTABLE, variando do simples ao complexo.

Outras dicas

Mesmo no caso de você mostrar, duvido que qualquer compilador faça o que você sugere.

Você está usando um programa muito simples, tudo em um arquivo.

Imagine que você teve foobase e alimentos em foo.h e foo.cpp e main em main.cpp. Ao compilar foo.cpp, como o compilador deve saber que em todo o programa não há necessidade de um VTBL. Não viu main.cpp.

A determinação final só pode ser feita no horário do link, quando é tarde demais e complicado para modificar o arquivo de objeto, encontre todas as chamadas para o tamanho do (alimentado), etc.

Mesmo os compiladores mais modernos e globalmente otimizados ainda seguem o princípio da "tradução independente".De acordo com este princípio, cada unidade de tradução é compilada de forma independente, sem qualquer conhecimento sobre quaisquer outras unidades de tradução que possam existir em todo o programa final.

Quando você declara uma classe com ligação externa, esta classe pode ser usada em outras unidades de tradução.O compilador não tem ideia de como sua classe pode ser usada nessas outras unidades de tradução.Portanto, é necessário assumir que cada instância dessa classe pode, em geral, precisar que seu ponteiro de tabela virtual seja inicializado corretamente no momento da construção.

No seu exemplo, um compilador inteligente pode descobrir que o tipo dinâmico de *pFoo objeto é FooDerived.Isso permitiria ao compilador otimizar o código:para gerar uma chamada direta para FooDerived::bar funcionar em resposta a pFoo->bar() expressão (sem usar a tabela virtual).Mesmo assim, o compilador normalmente ainda teria que inicializar corretamente o ponteiro da tabela virtual em cada FooDerived objeto.

É possível para um compilador eliminar a sobrecarga indireta de chamadas de funções virtuais quando sabe qual método exato da classe será invocado.Este é o resultado de uma forma de aponta para análise que os compiladores de otimização fazem.Eles rastreiam o fluxo de dados de variáveis ​​de ponteiro, e em qualquer lugar em que um ponteiro só possa apontar para objetos de um tipo, ele pode gerar chamadas exatamente para esse tipo, implementando a semântica de chamada virtual estaticamente (ou seja,em tempo de compilação).

Como muitos outros já disseram, a menos que o compilador esteja otimizando todo o programa/tempo de link (tornando-se mais popular;GCC ganha isso na versão 4.5), ele ainda precisa gerar algum tipo de tabela de funções virtuais para suportar compilação separada.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top