Примечание: Ответ на этот Вопрос является чисто специфическим для реализации, стандарт C ++ даже не упоминает виртуальную таблицу или виртуальные указатели, поэтому компиляторы могут свободно реализовывать динамическую диспетчеризацию с любым механизмом, стандарт определяет ожидаемое поведение, и пока компиляторы удовлетворяют этим требованиям, они могут осуществлять динамическую диспетчеризацию с использованием любого механизма, который они выберут.
Сказав вышесказанное, все известные компиляторы реализуют виртуальный механизм, используя механизмы vtable
и vptr
.
vtable
- это таблица, в которой хранятся адреса каждого virtual
метода этого класса или классов, из которых он получен.
vptr
указывает на vtable
этого класса.
vptr
похоронен где-то в указателе this
.
Всякий раз, когда производный класс переопределяет метод, компилятор заменяет адрес этой конкретной функции в vtable
на адрес переопределяемого метода. Если переопределения нет, vtable
продолжает удерживать адрес метода базового класса.
Например:
Рассмотрим следующую иерархию классов:
class shape
{
public:
virtual void Draw1();
virtual void Draw2();
virtual void Draw3();
};
vtable
для этого класса выглядит так:
Рассмотрим производный класс:
class line: public shape
{
public:
virtual void Draw1();
virtual void Draw2();
};
vtable
для этого класса будет:
Во время выполнения сначала vptr
выбирается из this
, а затем адрес вызываемой функции выбирается из соответствующего слота в vtable
, а затем вызывается метод. Поскольку каждый класс имеет свою собственную таблицу с адресом методов переопределения, можно вызывать соответствующий метод переопределения.
Таким образом, чтобы динамическая диспетчеризация работала, компилятору необходимо создать отдельные vtable
для каждого класса.
Хорошо читать:
Что происходит в оборудовании, когда я вызываю виртуальную функцию? Сколько слоев косвенности есть? Сколько там накладных расходов?