Se classes com funções virtuais são implementadas com vtables, como é implementada uma classe sem funções virtuais?

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

  •  01-07-2019
  •  | 
  •  

Pergunta

Em particular, não deveria haver algum tipo de ponteiro de função instalado?

Foi útil?

Solução

As funções de membro não virtual são realmente apenas um açúcar sintático, pois são quase como uma função comum, mas com verificação de acesso e um parâmetro de objeto implícito.

struct A 
{
  void foo ();
  void bar () const;
};

é basicamente o mesmo que:

struct A 
{
};

void foo (A * this);
void bar (A const * this);

O VTable é necessário para que chamamos a função correta para nossa instância de objeto específica. Por exemplo, se tivermos:

struct A 
{
  virtual void foo ();
};

A implementação de 'foo' pode se aproximar de algo como:

void foo (A * this) {
  void (*realFoo)(A *) = lookupVtable (this->vtable, "foo");
  (realFoo)(this);   // Make the call to the most derived version of 'foo'
}

Outras dicas

Eu acho que a frase "Classes com funções virtuais são implementadas com vtables"Está enganando você.

A frase faz parecer que classes com funções virtuais são implementadas "de maneira a"E classes sem funções virtuais são implementadas"da maneira b".

Na realidade, aulas com funções virtuais, além de Sendo implementados como as classes, eles também têm um VTable. Outra maneira de ver é que "'vtables' implementam a parte da 'função virtual' de uma classe".

Mais detalhes sobre como os dois funcionam:

Todas as classes (com métodos virtuais ou não virtuais) são estruturas. o A diferença entre uma estrutura e uma classe em C ++ é que, por padrão, os membros são públicos em estruturas e privados em classes. Por causa disso, usarei o termo classe aqui para me referir a estruturas e classes. Lembre -se, eles são quase sinônimos!

Membros de dados

As classes são (assim como as estruturas), apenas blocos de memória contígua, onde cada membro é armazenado em sequência. Observe que algumas vezes haverá lacunas entre os membros por razões arquitetônicas da CPU, para que o bloco possa ser maior que a soma de suas partes.

Métodos

Métodos ou "funções de membro" são uma ilusão. Na realidade, não existe uma "função de membro". Uma função é sempre apenas uma sequência de instruções de código da máquina armazenadas em algum lugar na memória. Para fazer uma chamada, o processador salta para essa posição de memória e começa a executar. Você poderia dizer que todos os métodos e funções são "globais", e qualquer indicação do contrário é uma ilusão conveniente aplicada pelo compilador.

Obviamente, um método age como pertence a um objeto específico, então claramente há mais acontecendo. Para vincular uma chamada específica de um método (uma função) a um objeto específico, todo método de membro tem um argumento oculto que é um ponteiro para o objeto em questão. O membro é escondido Por isso, você não o adiciona ao seu código C ++, mas não há nada de mágico nisso - é muito real. Quando você diz isso:

void CMyThingy::DoSomething(int arg);
{
    // do something
}

O compilador verdade isso faz isso:

void CMyThingy_DoSomething(CMyThingy* this, int arg)
{
    /do something
}

Finalmente, quando você escreve isso:

myObj.doSomething(aValue);

O compilador diz:

CMyThingy_DoSomething(&myObj, aValue);

Não há necessidade de ponteiros de função em qualquer lugar! O compilador já sabe qual método você está chamando, por isso chama diretamente.

Os métodos estáticos são ainda mais simples. Eles não têm um isto Ponteiro, para que eles sejam implementados exatamente como você os escreve.

É isso! O restante é apenas uma sintaxe de açúcar conveniente: o compilador sabe a qual método de classe A pertence, por isso garante que não permita que você chame a função sem especificar qual deles. Ele também usa esse conhecimento para traduzir myItem para this->myItem Quando é inequívoco fazê -lo.

(sim, isso mesmo: o acesso ao membro em um método é sempre feito indiretamente através de um ponteiro, mesmo que você não veja um)

(Editar: Removido a última frase e publicada separadamente para que possa ser criticada separadamente)

Os métodos virtuais são necessários quando você deseja usar polimorfismo.O virtual O modificador coloca o método no VMT para ligação tardia e então, em tempo de execução, é decidido qual método de qual classe será executado.

Se o método não for virtual - é decidido em tempo de compilação a partir de qual instância de classe ele será executado.

Ponteiros de função são usados ​​principalmente para retornos de chamada.

Se uma classe com uma função virtual for implementada com uma vtable, uma classe sem função virtual será implementada sem um VTable.

Um VTable contém os ponteiros de função necessários para despachar uma chamada para o método apropriado. Se o método não for virtual, a chamada vai para o tipo conhecido da classe e nenhum indireção é necessário.

Para um método não virtual, o compilador pode gerar uma invocação de função normal (por exemplo, CALL para um endereço específico com este ponteiro passado como parâmetro) ou até mesmo incorporá-lo.Para uma função virtual, o compilador geralmente não sabe em tempo de compilação em qual endereço invocar o código; portanto, ele gera um código que procura o endereço na tabela v em tempo de execução e então invoca o método.É verdade que mesmo para funções virtuais o compilador às vezes pode resolver corretamente o código correto em tempo de compilação (por exemplo, métodos em variáveis ​​locais invocados sem um ponteiro/referência).

(Puxei esta seção da minha resposta original para que ela possa ser criticada separadamente. É muito mais conciso e, a ponto de sua pergunta, então, de certa forma, é uma resposta muito melhor)

Não, não há ponteiros de função; Em vez disso, o compilador gira o problema De dentro para fora.

O compilador chama uma função global com um ponteiro para o objeto Em vez de ligar para alguns Ponto para funcionar dentro do objeto

Por quê? Porque geralmente é muito mais eficiente dessa maneira. As chamadas indiretas são instruções caras.

Não há necessidade de ponteiros de função, pois não podem mudar durante o tempo de execução.

As filiais são geradas diretamente para o código compilado para os métodos; Assim como se você tiver funções que não estiverem em uma aula, as filiais são geradas diretamente para eles.

Os links do compilador/vinculador diretamente quais métodos serão invocados. Não há necessidade de uma indireção vtable. BTW, o que isso tem a ver com "Stack vs. Heap"?

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