Как компилятор C++ знает, какую реализацию виртуальной функции вызывать?

вот пример полиморфизма из http://www.cplusplus.com/doc/tutorial/polymorphism.html (отредактировано для удобства чтения):

// 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;
}

мой вопрос в том, как компилятор знает, что ppoly1 является прямоугольником, а ppoly2-треугольником, чтобы он мог вызвать правильную функцию area ()? Это можно выяснить, посмотрев на линию "Polygon * ppoly1 = & rect;" и зная, что rect является прямоугольником, но это не сработает во всех случаях, не так ли? Что, если ты ... что-то вроде этого?

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

предполагая, что вам разрешен доступ к этой случайной области памяти.

Я бы проверил это, но я не могу на компьютере, на котором я сейчас.

(надеюсь, я не упускаю что-то очевидное...)

7 ответов


каждый объект (который принадлежит классу С хотя бы одной виртуальной функцией) имеет указатель, называемый vptr. Он указывает на vtbl его фактического класса (который каждый класс с виртуальными функциями имеет по крайней мере один из; возможно, более одного для некоторых сценариев множественного наследования).

на vtbl содержит кучу указателей, по одному для каждой виртуальной функции. Поэтому во время выполнения, код просто использует объект vptr найти vtbl, и оттуда адрес фактическая переопределенная функция.

в вашем конкретном случае, Polygon, Rectangle и Triangle каждого vtbl, каждый с одной записью, указывающей на его соответствующий area метод. Ваш ppoly1 будет vptr указывая на Rectangle ' s vtbl и ppoly2 аналогично и с Triangle ' s vtbl. Надеюсь, это поможет!


Крис Шут-Молодой дает основной ответ на этот вопрос.

Википедия имеет более глубокую обработку.

Если вы хотите знать полную информацию о том, как работает этот тип вещи (и для всех типов наследования, включая множественное и виртуальное наследование), одним из лучших ресурсов является "внутри объектной модели C++".


игнорируя аспекты привязки, на самом деле это не компилятор, который определяет это.

это среда выполнения C++, которая оценивает через vtables и vpointers, что на самом деле является производным объектом во время выполнения.

Я настоятельно рекомендую книгу Скотта Мейера Effective C++ для хорошего описания того, как это делается.

даже охватывает, как параметры по умолчанию в методе в производном классе игнорируются и любые параметры по умолчанию в базовом классе по-прежнему принимаются! Это обязывает.



чтобы ответить на вторую часть вашего вопроса: этот адрес, вероятно, не будет иметь v-таблицы в нужном месте, и начнется безумие. Кроме того, он не определен в соответствии со стандартом.


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

этот код к катастрофе. Компилятор скомпилирует его правильно, но когда дело доходит до времени выполнения, вы не будете указывать на допустимую V-таблицу, и если Вам ПОВЕЗЕТ, программа просто рухнет.

В C++ вы не должны использовать старые слепки в стиле C, как это, вы должны использовать операцию dynamic_cast вот так:

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

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

dynamic_cast вернет NULL, если данный указатель не является допустимым объектом Polygon, поэтому он будет захвачен УТВЕРЖДАТЬ.


таблицы виртуальных функций. А именно, оба ваших полигональных объекта имеют виртуальную таблицу функций, содержащую указатели функций на реализации всех их (нестатических) функций; и при создании экземпляра треугольника указатель виртуальной функции для функции area() указывает на функцию Triangle::area (); при создании экземпляра прямоугольника функция area() указывает на функцию Rectangle::area (). Поскольку указатели виртуальных функций хранятся вместе с данными для объекта в памяти каждый раз, когда вы ссылаетесь на этот объект как на многоугольник, будет использоваться соответствующая область() для этого объекта.