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

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

  •  03-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

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

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

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

Я настоятельно рекомендую книгу Скотта Мейера "Эффективный 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, поэтому он будет захвачен ASSERT.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top