Как компилятор 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 = ▭
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 (). Поскольку указатели виртуальных функций хранятся вместе с данными для объекта в памяти каждый раз, когда вы ссылаетесь на этот объект как на многоугольник, будет использоваться соответствующая область() для этого объекта.