Виртуальный стол / таблица диспетчеризации
Из того, что я знаю о CPP, у каждого класса есть свой vtable.
Однако эта ссылка в Википедии упоминает:
Таблица диспетчеризации объекта будет содержать адреса динамически связанных методов объекта. Вызовы метода выполняются путем извлечения адреса метода из таблицы диспетчеризации объекта. Таблица диспетчеризации одинакова для всех объектов, принадлежащих к одному и тому же классу, и, следовательно, обычно распределяется между ними.
Может кто-нибудь, пожалуйста, пролить немного света.
Спасибо!
4 ответа
Иногда это легче понять на примере:
class PureVirtual {
public:
virtual void methodA() = 0;
virtual void methodB() = 0;
};
class Base : public PureVirtual {
public:
virtual void methodA();
void methodC();
private:
int x;
};
class Derived : public Base {
public:
virtual void methodB();
private:
int y;
};
Итак, для объекта типа Derived это может выглядеть так:
------------
Known offset for vtable | 0xblah | -------> [Vtable for type "Derived"]
------------
Known offset for x | 3 |
------------
Known offset for y | 2 |
------------
С Vtable для типа "Производные" выглядит примерно так:
------------
Known offset for "methodA" | 0xblah1 | ------> methodA from Base
-------------
Known offset for "methodB" | 0xblah2 | ------> methodB from Derived
-------------
Обратите внимание, что поскольку methodC не был виртуальным, он вообще отсутствует в vtable. Также обратите внимание, что все экземпляры класса Derived будут иметь указатель vtable на один и тот же общий объект vtable (поскольку они имеют одинаковый тип).
Хотя реализации для C++ и Java немного отличаются, идеи не являются несовместимыми. Ключевое отличие, с концептуальной точки зрения, состоит в том, что методы Java являются "виртуальными", если они не объявлены как "окончательные". В C++ ключевое слово "virtual" должно быть задано явно, чтобы функция была в vtable. Все, что не в vtable, будет отправлено с использованием типов времени компиляции, а не типа времени выполнения объекта.
Да, виртуальные методы обрабатываются по-разному компилятором и средой выполнения.
Java: все методы в Java являются виртуальными по умолчанию. Это означает, что любой метод может быть переопределен при использовании в наследовании, если только этот метод не объявлен как final или static.
Из спецификации ВМ,
Виртуальная машина Java не требует какой-либо конкретной внутренней структуры для объектов. В книге отмечается, что в некоторых реализациях Sun виртуальной машины Java ссылка на экземпляр класса представляет собой указатель на дескриптор, который сам является парой указателей: один на таблицу, содержащую методы объекта, и указатель объекту Class, который представляет тип объекта, а другой - памяти, выделенной из кучи для данных объекта.
C++:
Всякий раз, когда функция-член класса объявляется как виртуальная, компилятор создает в памяти виртуальную таблицу, которая содержит все указатели функций, объявленные в этом классе как виртуальные. Это включает полиморфизм во время выполнения (т.е. обнаружение желаемой функции во время выполнения). Таблицы виртуальных функций также имеют дополнительный указатель в объекте на vtable. Поскольку этот дополнительный указатель и виртуальная таблица увеличивают размер объекта, разработчик класса должен быть осторожен с объявлением функций виртуальными.
Последовательность событий после вызова метода на указателе базового объекта:
- Получить указатель vtable (этот указатель vtable указывает на начало vtable).
- Получить указатели на функции в виртуальной таблице с помощью смещения.
Вызовите функцию косвенно через указатель vtable.
Каждый класс, имеющий виртуальные функции (т.е. в Java это просто "каждый класс"), имеет свой собственный vtable
, Каждый объект скрыл ссылку на свой класс vtable. Таким образом, объекты одного класса имеют идентичные ссылки.
Когда вы делаете вызов виртуального метода, компилятор обычно делает поиск:
obj.method(args);
переводится во что-то
obj.vtable[idx_method](obj, args);
Иногда, если компилятор может определить конкретный тип объекта, он может генерировать статический вызов вместо виртуального. Итак, код
MyObject obj(ctor_args);
....
obj.method(args);
можно перевести на
MyObject_method(obj, args);
Который обычно будет выполняться быстрее, чем виртуальный вызов.
Эта цитата указывает на то, что у каждого объекта есть таблица диспетчеризации, которая может быть общей для класса, поскольку они одинаковы для всех экземпляров одного и того же класса. IE Каждый класс имеет свой собственный vtable.