Таблица виртуальных методовТаблица виртуальных методов (англ. virtual method table, VMT) — координирующая таблица или vtable — механизм, используемый в языках программирования для поддержки динамического соответствия (или метода позднего связывания). Допустим, программа содержит несколько классов в иерархии наследования: базовый класс Когда программа вызывает метод Существует множество различных способов реализации подобного динамического связывания, но решение при помощи виртуальной таблицы весьма распространено в C++ и родственных языках (как например, D и C#). Языки, в которых есть разделение на программный интерфейс объектов и их реализацию, как Visual Basic и Delphi, также склоняются к использованию аналогов виртуальной таблицы, так как это позволяет объектам использовать другую реализацию просто используя другой набор указателей метода. РеализацияКоординирующая таблица объекта содержит адреса динамически связанных методов объекта. Метод вызывается при выборке адреса метода из таблицы. Координирующая таблица будет той же самой для всех объектов, принадлежащих тому же классу, поэтому допускается её совместное использование. Объекты, принадлежащие классам, совместимым по типу (например, стоящие на одной ступени в иерархии наследования), будут иметь схожие координирующие таблицы: адрес данного метода зафиксируется с одним и тем же смещением для всех классов, совместимых по типу. Таким образом, выбирая адрес метода из данной координирующей таблицы смещением, получим метод, связанный с текущим классом объекта.[1] В стандартах C++ нет четкого определения как должна реализовываться динамическая координация, но компиляторы зачастую используют некоторые вариации одной и той же базовой модели. Обычно компилятор создает отдельную виртуальную таблицу для каждого класса. После создания объекта указатель на эту виртуальную таблицу, называемый виртуальный табличный указатель или vpointer (также иногда называется vptr или vfptr), добавляется как скрытый член данного объекта (а зачастую как первый член). Компилятор также генерирует «скрытый» код в конструкторе каждого класса для инициализации vpointer'ов его объектов адресами соответствующей vtable. ПримерРассмотрим следующие объявления класса в C++: class B1 {
public:
void f0() {}
virtual void f1() {}
int int_in_b1;
};
class B2 {
public:
virtual void f2() {}
int int_in_b2;
};
используем для создания следующего класса: class D : public B1, public B2
{
public:
void d() {}
void f2() {} // переопределяем B2::f2()
int int_in_d;
};
и следующий фрагмент C++ кода: B2 *b2 = new B2();
D *d = new D();
G++ 3.4.6 из набора GCC создает следующую 32-битную схему памяти для объекта b2: +0: указатель на ТВМ B2 +4: значение int_in_b2 ТВМ B2: +0: B2::f2() а для объекта d: +0: указатель на ТВМ D (для B1) +4: значение int_in_b1 +8: указатель на ТВМ D (для B2) +12: значение int_in_b2 +16: значение int_in_d Общий размер: 20 байтов. ТВМ D (для B1): +0: B1::f1() // B1::f1() не переопределён ТВМ D (для B2): +0: D::f2() // B2::f2() замещён D::f2() Необходимо отметить, что невиртуальные функции (такие как Переопределение метода Множественное наследованиеМножественное наследование классов Рассмотрим следующий C++ код: D *d = new D();
B1 *b1 = dynamic_cast<B1*>(d);
B2 *b2 = dynamic_cast<B2*>(d);
В то время как ВызовВызов В случае одиночного наследования (или в случае языка с поддержкой только одиночного наследования), если vpointer всегда является первым элементом в *((*d)[0])(d)
В более общем случае, как упоминалось выше, вызов *((d->/*указатель ТВМ D (для B1)*/)[0])(d) // d->f1();
*((d->/*указатель ТВМ D (для B2)*/)[0])(d+8) // d->f2();
*((/*адрес ТВМ B2 */)[0])(d+8) // d->B2::f2();
Для сравнения, вызов *B1::f0(d)
ЭффективностьВиртуальный вызов требует как минимум дополнительно индексированного разыменования, а иногда дополнительной «адресной привязки» (fixup), схожей с невиртуальным вызовом, который является простым переходом к скомпилированному указателю. Поэтому вызов виртуальных функций по сути медленнее, чем вызов невиртуальных. Эксперимент, проведённый в 1996 году, показал, что примерно 6-13% времени выполнения тратится просто на поиск соответствующей функции, в то время как общий рост времени выполнения может достичь 50%[2]. Стоимость использования виртуальных функций на современных архитектурах процессоров может быть не столь высока из-за наличия значительно больших кэшей и лучшего предсказания переходов. В среде где JIT-компиляция не используется, вызовы виртуальных функций обычно не могут быть внутренними. В то время как компилятор может заменить просмотр и непрямой вызов, например, условным выполнением каждого внутреннего тела, подобная оптимизация не распространена. Для избежания подобных потерь компиляторы обычно избегают использования виртуальных таблиц всегда, когда вызов может быть выполнен во время компиляции. Таким образом, вышеприведеный вызов Сравнение с альтернативамиВиртуальная таблица в общем случае жертвует производительностью для достижения динамического выбора, но существует множество альтернатив ей, как например, выбор по двоичному дереву, обладающий более высокой производительностью, но различной скоростью исполнения[3]. Тем не менее, виртуальная таблица предусмотрена только для единичной диспетчеризации (single dispatch) по специальному параметру "this", в отличие от множественной диспетчеризации (multiple dispatch) (как в CLOS или Dylan), где типы всех параметров могут быть присвоены в ходе диспетчеризации. Виртуальная таблица также работает только если диспетчеризация ограничена известным набором методов, поэтому множество виртуальных таблиц могут быть помещены в простой массив во время компиляции, в отличие от языков с поддержкой утиной типизации (например, Smalltalk, Python или JavaScript). Языки, поддерживающие один или оба этих варианта, часто осуществляют диспетчеризацию при помощи поиска строки в хеш-таблице или другого эквивалентного метода. Существует довольно большое число уловок повысить скорость (например, токенизация имен методов, применение кэширования, JIT-компиляции), а время диспетчеризации часто не производит значимого влияния на общее время обработки, но несмотря на это, просмотр виртуальной таблицы заметно быстрее ссылка?. Виртуальную таблицу также проще реализовывать и отлаживать, а кроме того еще и ближе к "философии языка Си" нежели хеш-таблицы строкссылка?. См. такжеПримечания
Источники
Ссылки
|
Portal di Ensiklopedia Dunia