Как компилятор C ++ знает, какую реализацию виртуальной функции вызывать? - PullRequest
9 голосов
/ 15 октября 2008

Вот пример полиморфизма из 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 это Rectangle, но это не сработает во всех случаях, не так ли? Что делать, если вы сделали что-то подобное?

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

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

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

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

Ответы [ 6 ]

24 голосов
/ 15 октября 2008

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

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

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

6 голосов
/ 15 октября 2008

Крис Шестер-Янг дает основной ответ на этот вопрос.

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

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

3 голосов
/ 15 октября 2008

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

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

Я очень рекомендую книгу Скотта Мейера «Эффективный С ++» для хорошего описания того, как это делается.

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

1 голос
/ 15 октября 2008

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

1 голос
/ 15 октября 2008
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, поэтому он будет пойман ASSERT.

1 голос
/ 15 октября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...