Como o compilador C ++ saber que a implementação de uma função virtual para chamar?

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

  •  03-07-2019
  •  | 
  •  

Pergunta

Aqui está um exemplo de polimorfismo de http://www.cplusplus.com/doc /tutorial/polymorphism.html (editado para facilitar a leitura):

// abstract base class
#include <iostream>
using namespace std;

class Polygon {
    protected:
        int width;
        int height;
    public:
        void set_values(int a, int b) { width = a; height = b; }
        virtual int area(void) =0;
};

class Rectangle: public Polygon {
    public:
        int area(void) { return width * height; }
};

class Triangle: public Polygon {
    public:
        int area(void) { return width * height / 2; }
};

int main () {
    Rectangle rect;
    Triangle trgl;
    Polygon * ppoly1 = &rect;
    Polygon * ppoly2 = &trgl;
    ppoly1->set_values (4,5);
    ppoly2->set_values (4,5);
    cout << ppoly1->area() << endl; // outputs 20
    cout << ppoly2->area() << endl; // outputs 10
    return 0;
}

A minha pergunta é como é que o compilador sabe que ppoly1 é um rectângulo e que ppoly2 é um triângulo, de modo que ele pode chamar a área correta function ()? Ele poderia descobrir isso por olhar para a linha "Polígono * ppoly1 = ?" e sabendo que rect é um retângulo, mas isso não iria funcionar em todos os casos, não é? E se você fez algo assim?

cout << ((Polygon *)0x12345678)->area() << endl;

Assumindo que você está autorizado a acessar essa área aleatória de memória.

Gostaria de testar isso, mas não posso no computador que eu estou no momento.

(Espero que eu não estou faltando algo óbvio ...)

Foi útil?

Solução

Cada objeto (que pertence a uma classe com pelo menos uma função virtual) tem um ponteiro, chamado de vptr. Ele aponta para a vtbl da sua classe real (que cada classe com funções virtuais tem pelo menos um de, possivelmente mais de um para alguns cenários de herança múltipla).

O vtbl contém um monte de ponteiros, um para cada função virtual. Assim, em tempo de execução, o código só usa vptr do objeto para localizar o vtbl, ea partir daí o endereço da função substituído real.

No seu caso específico, Polygon, Rectangle e Triangle cada um tem uma vtbl, cada um com uma entrada apontando para o seu método area relevante. Seu ppoly1 terá um apontador vptr para Rectangle de vtbl e ppoly2 semelhante com Triangle de vtbl. Espero que isso ajude!

Outras dicas

Chris Jester-Young dá a resposta básica para esta questão.

Wikipedia tem um mais no tratamento profundidade.

Se você quiser saber os detalhes de como este tipo de coisa funciona (e para todo o tipo de herança, incluindo múltipla e herança virtual), um dos melhores recursos é Stan Lippman do " no interior do C ++ Object Model ".

Desconsiderando os aspectos da ligação, não é realmente o compilador que determina isso.

É o tempo de execução C ++ que avalia, através de vtables e vpointers, o que o objeto derivado realmente é em tempo de execução.

Eu recomendo o livro de Scott Meyer eficaz C ++ para boas descrições sobre como isso é feito.

tampas até mesmo como parâmetros padrão em um método em uma classe derivada são ignorados e quaisquer parâmetros padrão em uma classe base ainda são tomadas! Isso é obrigatório.

Para responder à segunda parte da sua pergunta: esse endereço provavelmente não terá um v-table no lugar certo, e loucura seguirá. Além disso, ela é indefinida de acordo com o padrão.

cout << ((Polygon *)0x12345678)->area() << endl;

Este código é um desastre esperando para acontecer. O compilador irá compilar tudo certo, mas quando se trata de tempo de execução, você não vai estar apontando para a v-table válido e se você tiver sorte o programa só irá falhar.

Em C ++, você não deve usar moldes de estilo C antigos como este, você deve usar dynamic_cast assim:

Polygon *obj = dynamic_cast<Polygon *>(0x12345678)->area();
ASSERT(obj != NULL);

cout << obj->area() << endl;

dynamic_cast retornará NULL se o ponteiro dado não é um objeto Polygon válidas por isso vai ser preso pela ASSERT.

tabelas de função virtual. A saber, tanto de seus objetos Polygon derivados têm uma tabela de função virtual que contém ponteiros de função para as implementações de todas as suas funções (não-estáticos); e quando você instanciar um triângulo, o ponteiro de função virtual para a área () função aponta para Triangle :: área de () função; quando você instanciar uma função area () Retângulo, os pontos de função area () para o retângulo ::. Porque ponteiros de função virtuais são armazenados junto com os dados de um objeto na memória, cada vez que você faz referência a esse objeto como um Polygon, a área apropriada () para esse objeto será usado.

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