Virtual function table

Virtual function table

Таблица виртуальных методов (англ. virtual method table, VMT) - координирующая таблица или vtable - механизм, используемый в языках программирования для поддержки динамического соответствия (или метода позднего связывания).

Допустим, программа содержит несколько классов в иерархии наследования: суперкласс Cat и два подкласса HouseCat и Lion. Класс Cat определяет виртуальную функцию speak, так что его подклассы могут обеспечивать соответствующую реализацию (т.е. или "мяу" или "рык").

Когда программа вызывает метод speak по указателю Cat (который может указывать на класс Cat или любой подкласс Cat), контекстное окружение (среда запуска) должна уметь определять, какая именно реализация вызывается, в зависимости от текущего типа указываемого объекта.

Существует множество различных способов реализации подобного динамического связывания, но решение при помощи vtable весьма распространено в C++ и родственных языках (как например, D и C#). Языки, в которых есть разделение на программный интерфейс объектов и их реализацию, как Visual Basic и Delphi, также склоняются к использованию аналогов vtable, так как это позволяет объектам использовать другую реализацию просто используя другой набор указателей метода.

Содержание

Реализация

Координирующая таблица объекта содержит адреса динамически связанных методов объекта. Метод вызывается при выборке адреса метода из таблицы. Координирующая таблица будет той же самой для всех объектов, принадлежащих тому же классу, поэтому допускается ее совместное использование. Объекты, принадлежащие классам, совместимым по типу (например, стоящие на одной ступени в иерархии наследования), будут иметь схожие координирующие таблицы: адрес данного метода зафиксируется с одним и тем же самым смещением для всех классов, совместимых по типу. Таким образом, выбирая адрес метода из данной координирующей таблицы смещением получим метод, связанный с текущим классом объекта.[1]

В стандартах C++ нет четкого определения как должна реализовываться динамическая координация, но компиляторы зачастую используют некоторые вариации одной и той же базовой модели.

Обычно компилятор создает отдельную vtable для каждого класса. После создания объекта указатель на эту vtable, называемый виртуальный табличный указатель или vpointer, добавляется как скрытый член данного объекта (а зачастую как первый член). Компилятор также генерирует "скрытый" код в конструкторе каждого класса для инициализации 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:[nb 1]

b2:
  +0: указатель на таблицу виртуальных методов B2
  +4: значение int_in_b2

таблица виртуальных методов B2:
  +0: B2::f2()   


а для объекта d схема памяти будет такой:

d:
  +0: указатель на ТВМ D (для B1)
  +4: значение int_in_b1
  +8: указатель на ТВМ D (для B2)
 +12: значение int_in_b2
 +16: значение int_in_d

Всего: 20 Bytes.

ТВМ D (для B1):
  +0: B1::f1()  // B1::f1() не переопределена

ТВМ D (для B2):
  +0: D::f2()   // B2::f2() переопределена D::f2()

Необходимо отметить, что невиртуальные функции (такие как f0) в общем случае не могут появляться в vtable, но в некоторых случаях есть исключения (как, например, конструктор по умолчанию).

Переопределение метода f2() в классе D реализуется дублированием ТВМ B2 и заменой указателя на B2::f2() указателем на D::f2().

Множественное наследование

Компилятор C++ реализует множественное наследование классов B1 и B2 в класс D, используя две таблицы виртуальных методов, по одной для каждого базового класса. (Есть и другие способы реализации множественного наследования, но данный наиболее распространенный.) Это приводит к потребности в "указателях на адресную запись" (связках) при создании.

Рассмотрим следующий C++ код:

D  *d  = new D();
B1 *b1 = dynamic_cast<B1*>(d);
B2 *b2 = dynamic_cast<B2*>(d);

В то время как d и b1 указывают на одно место в памяти после выполнения данного кода, b2 будет указывать на участок памяти d+8 (смещение на восемь байт относительно участка d). Таким образом, b2 указывает на область памяти внутри d, что "выглядит" как сущность B2, т.е. имеет ту же схему размещения в памяти, что и сущность B2.

Вызов

Вызов d->f1() происходит при разыменовании vpointer D::B1 из d: просмотр записи о f1 в vtable, а затем разыменование этого указателя вызывает код.

В случае одиночного наследования (или в случае языка с поддержкой только одиночного наследования), если vpointer всегда является первым элементом в d (как это происходит у многих компиляторов), то это решается следующим псевдо-C++ кодом:

*((*d)[0])(d)

В более общем случае, как упоминалось выше, вызов f1(), D::f2() и B2::f2() на d будет сложнее

*((d->/*указатель на таблицу виртуальных методов D (для B1)*/)[0])(d)
*((d->/*указатель на таблицу виртуальных методов D (для B1)*/)[12])(d)
*((d->/*указатель на таблицу виртуальных методов D (для B2)*/)[0])(d+8)

Для сравнения, вызов d->f0() гораздо проще:

*B1::f0(d)

Эффективность

Виртуальный вызов требует как минимум дополнительно индексированного разыменования, а иногда дополнительной "адресной привязки" (fixup), схожей с невиртуальным вызовом, который является простым переходом к скомпилированному указателю. Поэтому вызов виртуальных функций по сути медленнее, чем вызов невиртуальных. Опыты показывают, что примерно 6-13% времени исполнения тратится просто на поиск соответствующей функции, при этом дополнительные расходы могут достигать[2].

К тому же, в среде где

Для избежания подобных потерь компиляторы обычно избегают использования vtable всегда, когда вызов может быть выполнен во время компиляции.

Таким образом, вышеприведеный вызов f1 может и не требовать просмотра vtable, так как компилятор может сообщить о том, что d может иметь в этой точке только D, а D не переопределяет f1. Или компилятор (как вариант, оптимизатор) может обнаружить отсутствие подклассов B1 в программе, переопределяющей f1. Вызов B1::f1 или B2::f2 вероятно не потребует просмотра vtable благодаря реализации, определенной явным образом (хотя все еще требуется привязка по указателю 'this').

Сравнение с альтернативами

Vtable в общем случае жертвует производитльностью для достижения динамического выбора, но существует множство альтернатив ей, как например, выбор по двоичному дереву, обладающий более высокой производительностью, но различной скоростью исполнения[3].

Тем не менее, vtable предусмотрена только для единичной диспетчеризации (single dispatch) по специальному параметру "this", в отличие от множественной диспетчеризации (multiple dispatch) (как в Dylan), где типы всех параметров могут быть присвоены в ходе диспетчеризации.

Vtable также работает только если диспетчеризация ограничена известным набором методов, поэтому множество vtable могут быть помещены в простой массив во время компиляции, в отличие от языков с поддержкой утиной типизации (например, Python или JIT-компиляции), а время диспетчеризации часто не производит значимого влияния на общее время обработки, но несмотря на это, просмотр vtable заметно быстрее. Vtable также проще реализовывать и отлаживать, а кроме того еще и ближе к "философии языка Си" нежели хэш-таблицы строк.

Дополнительные источники

Виртуальные функции – низкоуровневый взгляд (статья)

См. также

Примечания

  1. Аргумент G++ -fdump-class-hierarchy может быть использован для сброса ТВМ для "ручной" проверки. Для компилятора AIX VisualAge XlC используется -qdump_class_hierarchy для сброса иерархии классов и схемы ТВФ.

Источники

  1. Ellis & Stroustrup 1990, pp. 227–232
  2. Driesen, Karel and Hölzle, Urs, "The Direct Cost of Virtual Function Calls in C++", OOPSLA 1996
  3. Zendra, Olivier and Driesen, Karel, "Stress-testing Control Structures for Dynamic Dispatch in Java", Pp. 105–118, Proceedings of the USENIX 2nd Java Virtual Machine Research and Technology Symposium, 2002 (JVM '02)

Ссылки

  • Margaret A. Ellis and Bjarne Stroustrup (1990) The Annotated C++ Reference Manual. Reading, MA: Addison-Wesley. (ISBN 0-201-51459-1)

Wikimedia Foundation. 2010.

Игры ⚽ Нужен реферат?

Полезное


Смотреть что такое "Virtual function table" в других словарях:

  • Virtual method table — A virtual method table, virtual function table, dispatch table, or vtable, is a mechanism used in programming language to support dynamic dispatch (or run time method binding).Suppose a program contains several classes in an inheritance hierarchy …   Wikipedia

  • Virtual II — Virtual ] [ (pronounced virtual two ), is a software application that emulates the Apple II series of computers on an Apple Macintosh computer running Mac OS X. The emulator supports these 8 bit Apple II machines: * [Apple II series#Apple… …   Wikipedia

  • Virtual synchrony — is an interprocess messaging passing (sometimes called event queue management) technology. Virtual synchrony systems allow programs running in a network to organize themselves into process groups , and to send messages to groups (as opposed to… …   Wikipedia

  • Function object — A function object, also called a functor or functional, is a computer programming construct allowing an object to be invoked or called as if it were an ordinary function, usually with the same syntax.Function objects are unrelated to functors in… …   Wikipedia

  • Function pointer — A function pointer is a type of pointer in C, C++, D, and other C like programming languages, and Fortran 2003.[1] When dereferenced, a function pointer can be used to invoke a function and pass it arguments just like a normal function. In… …   Wikipedia

  • Branch table — In computer programming, a branch table (sometimes known as a jump table) is a term used to describe an efficient method of transferring program control (branching) to another part of a program (or a different program that may have been… …   Wikipedia

  • Dispatch table — In computer science, a dispatch table is a table of pointers to functions or methods. Use of such a table is a common technique when implementing late binding in object oriented programming. Perl implementation The following shows one way to… …   Wikipedia

  • Page table — A page table is the data structure used by a virtual memory system in a computer operating system to store the mapping between virtual addresses and physical addresses. Virtual addresses are those unique to the accessing process. Physical… …   Wikipedia

  • Control table — This simple control table directs program flow according to the value of the single input variable. Each table entry holds a possible input value to be tested for equality (implied) and a relevant subroutine to perform in the action column. The… …   Wikipedia

  • History of virtual learning environments 1990s — In the history of virtual learning environments, the 1990s was a time of growth, primarily due to advent of the affordable computer and of the Internet.1990s1990* Formal Systems Inc. of Princeton, NJ, USA introduces a DOS based Assessment… …   Wikipedia


Поделиться ссылкой на выделенное

Прямая ссылка:
Нажмите правой клавишей мыши и выберите «Копировать ссылку»