Віртуальна таблиця функцій, віртуальна таблиця методів (англ. virtual method table, VMT, vtable) — механізм, що використовується реалізаціями мов програмування для підтримки динамічної диспетчеризації (або зв'язування методів під час виконання).
Припустимо, програма містить декілька класів в ієрархії спадкування: суперклас, Кіт
, і два підкласи, ДомашнійКіт
і Лев
. Нехай клас Кіт
визначає віртуальний метод з назвою звучати
, тоді вже його підкласи можуть забезпечити підходящу реалізацію (нявкання
або ревіння
).
Коли програма викликає метод звучати
у вказівника на Кіт
(що вказує на об'єкт класу Кіт
, або будь-якого його підкласу Кіт
), середовище виконання має бути в змозі через дійсний тип об'єкта, на який вказує вказівник, визначити, яку саме реалізацію він має викликати.
Існує багато шляхів здійснення такої динамічної диспетчеризації, але рішення за допомогою віртуальної таблиці особливо вживане серед і споріднених мов (таких як D і C#). Мови, в яких відділяють програмний інтерфейс об'єкта від реалізації, такі як Visual Basic і Delphi, також тяжіють до використання аналогів віртуальних таблиць.
Реалізація
Віртуальна таблиця об'єкта буде містити адреси динамічно зв'язаних методів. Виклики методів здійснюються шляхом вибирання адреси методу з віртуальної таблиці об'єкта. Віртуальна таблиця одна й та сама для всіх об'єктів одного класу, і через це зазвичай спільно використовується ними. Об'єкти, що належать до класів, сумісних за типом (наприклад, що знаходяться на одній сходинці в ієрархії спадкування), будуть мати віртуальні таблиці з однаковим розміщенням: адреса даного віртуального методу буде знаходитись з одним і тим самим зсувом для всіх класів, сумісних за типом. Таким чином, при вибиранні адреси методу з даного зсуву віртуальної таблиці поверне метод відповідний дійсному класу об'єкта
Як правило компілятор створює окрему віртуальну таблицю для кожного класу. Коли об'єкт уже створений, вказівник на його віртуальну таблицю, який зветься вказівник віртуальної таблиці (англ. virtual table pointer або англ. vpointer), додається як прихований член цього об'єкта (зазвичай як перший член ). Компілятор також генерує «прихований» код в конструкторі кожного класу для ініціалізації цього вказівника свого об'єкта адресою відповідної віртуальної таблиці.
Приклади
Припустимо наступні визначення класів в синтаксисі С++:
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; };
і наступний шматок коду:
B2 *b2 = new B2(); D *d = new D();
G++ 3.4.6 від GCC продукує наступне 32-бітове розміщення для об'єкта b2
:
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 Байтів. віртуальна таблиця методів D (для B1): +0: B1::f1() // B1::f1() не заміщений віртуальна таблиця методів D (для B2): +0: D::f2() // B2::f2() заміщений на D::f2()
Зауважте, що функції не позначені ключовим словом virtual
в своїх визначеннях (так як f0()
і d()
) зазвичай не з'являються в віртуальній таблиці методів. Хоча бувають винятки для особливих випадків, наприклад, для конструктора за замовченням.
Заміщення методу f2()
в класі D
реалізовано повторенням віртуальної таблиці методів B2
і заміною вказівника на B2::f2()
вказівником на D::f2()
.
Множинна спадковість і перехідники
Компілятор C++ здійснює множинну спадковість класів B1
і B2
в клас D
із використанням двох таблиць віртуальних методів, одної для кожного базового класу. (Загалом багато способів забезпечення множинної спадковості, але це найпоширеніший.) Це призводить до необхідності коригування вказівників перехідниками при приведені типів.
Припустимо наступний C++ код:
D *d = new D(); B1 *b1 = static_cast<B1*>(d); B2 *b2 = static_cast<B2*>(d);
Після виконання цього коду d
і b1
будуть вказувати на одну адресу в пам'яті, а b2
буде вказувати на адресу d+8
. Таким чином, b2
вказує на область пам'яті всередині d
, яка виглядає як'` екземпляр B2
, тобто, має таке саме роміщення в пам'яті як зразок B2
.
Викликання
Виклик d->f1()
виконується через розіменування вказівника таблиці віртуальних методів D::B1
в d
, пошуком запису f1
в віртуальній таблиці, і тоді розіменуванням знайденого вказівника для виклику.
У випадку одинарного спадкування, якщо вказівник завжди перший елемент в d
(як це і є в більшості компіляторів), то це згортається до наступного псевдо-C++:
(*((*d)[0]))(d)
Де *d посилається на таблицю віртуальних функцій D і [0] посилається на перший метод в таблиці. Параметр d стає вказівником «this» для об'єкта.
В загальнішому випадку, викликання B1::f1()
і D::f2()
складніше:
(*(*(d[+0]/*вказівник на таблицю віртуальних методів D (для B1)*/)[0]))(d) /* Call d->f1() */ (*(*(d[+8]/*вказівник на таблицю віртуальних методів D (для B2)*/)[0]))(d+8) /* Call d->f2() */
виклик d->f1() передає вказівник B1 як параметр. Виклик d->f2() передає вказівник B2 як параметр. Другий виклик вимагає коригування вказівника. Вказівник на B2::f2 знаходиться поза межами віртуальної таблиці для D.
Для порівняння, виклик d->f0()
значно простіший:
(*B1::f0)(d)
Ефективність
Віртуальний виклик вимагає щонайменше додаткового індексованого розіменування та, іноді, коригування вказівника, порівняно з невіртуальним викликом, який є простим переходом за скомпільованим вказівником. Через це, виклик віртуальних функцій значно повільніший за виклик невіртуальних. Експерименти показують, що близько 6-13% часу виконання припадає на перехід до коректної функції, хоча верхня межа досягає 50%. Виклик віртуальних функцій може бути не таким дорогим на процесорах із сучасною архітектурою завдяки значно більшому кешу і кращому передбаченню переходів.
У середовищах де не використовується JIT-компіляція, виклики віртуальних функцій не можуть бути (англ. inline). Компілятор може замінити пошук і непрямий виклик на, наприклад, умовне виконання кожного тіла функції, але така оптимізація нерозповсюджена.
Для уникнення додаткових дій, компілятори зазвичай уникають використання таблиці віртуальних функцій коли виклик може бути розв'язаним під час компіляції.
Таким чином, попередній виклик f1
може не вимагати перегляду віртуальної таблиці через те, що компілятор може визначити, щоd
в даному випадку може містити тільки D
, і D
не заміщує f1
. Або компілятор (чи оптимізатор) може бути в змозі визначити, що в програмі немає підкласів B1
, які заміщають f1
. Виклик B1::f1
або B2::f2
можливо не будуть вимагати перегляду віртуальної таблиці через явно визначену реалізацію (хоча коригування 'this'-вказівника все ще потрібно).
Порівняння з альтернативами
Віртуальна таблиця зазвичай є компромісом з досить хорошою виробністю для досягнення динамічної диспетчеризації, але існують альтернативи, такі як диспетчеризація за двійковим деревом, з більшою виробністю, але різною швидкістю виконання.
Однак, віртуальні таблиці дозволяють одиничну диспетчеризацію (англ. single dispatch), за спеціальним параметром this, на відміну від множинної диспетчеризації (як в CLOS або Dylan), де тип параметра береться до уваги під час диспетчеризації.
Віртуальні таблиці працюють тільки коли диспетчеризація обмежена відомим набором методів, тобто вони можуть бути розміщені в простих масивах створених під час компіляції, на відміну від мов з качиною типізацією (англ. Duck typing), таких як Smalltalk, Python або JavaScript.
Мови, що підтримують один або обидва цих методи часто здійснюють диспетчеризацію через пошук рядка в хеш-таблиці, або іншим подібним методом. Існує багато вивертів, які дозволяють пришвидшити це (наприклад, токенізація імен методів, кешування результатів пошуку, JIT компіляція), і час диспетчеризації немає значного впливу на загальний час виконання, проте використання віртуальних таблиць вочевидь швидше. Їх також легше реалізовувати і зневаджувати, також вони ближче до «C стилю» ніж хеш-таблиці рядків
Зауважимо, що Objective-C 2.1 підтримує диспетчеризації за допомогою віртуальних таблиць під 64-бітовою Mac OS X 10.6+.
Примітки
- Ellis & Stroustrup 1990, pp. 227—232
- . Архів оригіналу за 27 листопада 2012. Процитовано 11 вересня 2010.
- Driesen, Karel and Hölzle, Urs, "The Direct Cost of Virtual Function Calls in C++" [ 2017-08-10 у Wayback Machine.], OOPSLA 1996
- 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)
- http://arstechnica.com/apple/reviews/2009/08/mac-os-x-10-6.ars/5
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет
Virtualna tablicya funkcij virtualna tablicya metodiv angl virtual method table VMT vtable mehanizm sho vikoristovuyetsya realizaciyami mov programuvannya dlya pidtrimki dinamichnoyi dispetcherizaciyi abo zv yazuvannya metodiv pid chas vikonannya Pripustimo programa mistit dekilka klasiv v iyerarhiyi spadkuvannya superklas Kit i dva pidklasi DomashnijKit i Lev Nehaj klas Kit viznachaye virtualnij metod z nazvoyu zvuchati todi vzhe jogo pidklasi mozhut zabezpechiti pidhodyashu realizaciyu nyavkannya abo revinnya Koli programa viklikaye metod zvuchati u vkazivnika na Kit sho vkazuye na ob yekt klasu Kit abo bud yakogo jogo pidklasu Kit seredovishe vikonannya maye buti v zmozi cherez dijsnij tip ob yekta na yakij vkazuye vkazivnik viznachiti yaku same realizaciyu vin maye viklikati Isnuye bagato shlyahiv zdijsnennya takoyi dinamichnoyi dispetcherizaciyi ale rishennya za dopomogoyu virtualnoyi tablici osoblivo vzhivane sered C i sporidnenih mov takih yak D i C Movi v yakih viddilyayut programnij interfejs ob yekta vid realizaciyi taki yak Visual Basic i Delphi takozh tyazhiyut do vikoristannya analogiv virtualnih tablic RealizaciyaVirtualna tablicya ob yekta bude mistiti adresi dinamichno zv yazanih metodiv Vikliki metodiv zdijsnyuyutsya shlyahom vibirannya adresi metodu z virtualnoyi tablici ob yekta Virtualna tablicya odna j ta sama dlya vsih ob yektiv odnogo klasu i cherez ce zazvichaj spilno vikoristovuyetsya nimi Ob yekti sho nalezhat do klasiv sumisnih za tipom napriklad sho znahodyatsya na odnij shodinci v iyerarhiyi spadkuvannya budut mati virtualni tablici z odnakovim rozmishennyam adresa danogo virtualnogo metodu bude znahoditis z odnim i tim samim zsuvom dlya vsih klasiv sumisnih za tipom Takim chinom pri vibiranni adresi metodu z danogo zsuvu virtualnoyi tablici poverne metod vidpovidnij dijsnomu klasu ob yekta Yak pravilo kompilyator stvoryuye okremu virtualnu tablicyu dlya kozhnogo klasu Koli ob yekt uzhe stvorenij vkazivnik na jogo virtualnu tablicyu yakij zvetsya vkazivnik virtualnoyi tablici angl virtual table pointer abo angl vpointer dodayetsya yak prihovanij chlen cogo ob yekta zazvichaj yak pershij chlen Kompilyator takozh generuye prihovanij kod v konstruktori kozhnogo klasu dlya inicializaciyi cogo vkazivnika svogo ob yekta adresoyu vidpovidnoyi virtualnoyi tablici PrikladiPripustimo nastupni viznachennya klasiv v sintaksisi S class B1 public void f0 virtual void f1 int int in b1 class B2 public virtual void f2 int int in b2 vikoristayemo yih dlya inicializaciyi takih klasiv class D public B1 public B2 public void d void f2 zamishuye B2 f2 int int in d i nastupnij shmatok kodu B2 b2 new B2 D d new D G 3 4 6 vid GCC produkuye nastupne 32 bitove rozmishennya dlya ob yekta b2 b2 0 vkazivnik na tablicyu virtualnih metodiv dlya B2 4 znachennya int in b2 virtualna tablicya metodiv B2 0 B2 f2 i nastupne rozmishennya ob yekta d v pam yati d 0 vkazivnik na tablicyu virtualnih metodiv D dlya B1 4 znachennya int in b1 8 vkazivnik na tablicyu virtualnih metodiv D dlya B2 12 znachennya int in b2 16 znachennya int in d Zagalnij rozmir 20 Bajtiv virtualna tablicya metodiv D dlya B1 0 B1 f1 B1 f1 ne zamishenij virtualna tablicya metodiv D dlya B2 0 D f2 B2 f2 zamishenij na D f2 Zauvazhte sho funkciyi ne poznacheni klyuchovim slovom virtual v svoyih viznachennyah tak yak f0 i d zazvichaj ne z yavlyayutsya v virtualnij tablici metodiv Hocha buvayut vinyatki dlya osoblivih vipadkiv napriklad dlya konstruktora za zamovchennyam Zamishennya metodu f2 v klasi D realizovano povtorennyam virtualnoyi tablici metodiv B2 i zaminoyu vkazivnika na B2 f2 vkazivnikom na D f2 Mnozhinna spadkovist i perehidnikiKompilyator C zdijsnyuye mnozhinnu spadkovist klasiv B1 i B2 v klas D iz vikoristannyam dvoh tablic virtualnih metodiv odnoyi dlya kozhnogo bazovogo klasu Zagalom bagato sposobiv zabezpechennya mnozhinnoyi spadkovosti ale ce najposhirenishij Ce prizvodit do neobhidnosti koriguvannya vkazivnikiv perehidnikami pri privedeni tipiv Pripustimo nastupnij C kod D d new D B1 b1 static cast lt B1 gt d B2 b2 static cast lt B2 gt d Pislya vikonannya cogo kodu d i b1 budut vkazuvati na odnu adresu v pam yati a b2 bude vkazuvati na adresu d 8 Takim chinom b2 vkazuye na oblast pam yati vseredini d yaka viglyadaye yak ekzemplyar B2 tobto maye take same romishennya v pam yati yak zrazok B2 ViklikannyaViklik d gt f1 vikonuyetsya cherez rozimenuvannya vkazivnika tablici virtualnih metodiv D B1 v d poshukom zapisu f1 v virtualnij tablici i todi rozimenuvannyam znajdenogo vkazivnika dlya vikliku U vipadku odinarnogo spadkuvannya yaksho vkazivnik zavzhdi pershij element v d yak ce i ye v bilshosti kompilyatoriv to ce zgortayetsya do nastupnogo psevdo C d 0 d De d posilayetsya na tablicyu virtualnih funkcij D i 0 posilayetsya na pershij metod v tablici Parametr d staye vkazivnikom this dlya ob yekta V zagalnishomu vipadku viklikannya B1 f1 i D f2 skladnishe d 0 vkazivnik na tablicyu virtualnih metodiv D dlya B1 0 d Call d gt f1 d 8 vkazivnik na tablicyu virtualnih metodiv D dlya B2 0 d 8 Call d gt f2 viklik d gt f1 peredaye vkazivnik B1 yak parametr Viklik d gt f2 peredaye vkazivnik B2 yak parametr Drugij viklik vimagaye koriguvannya vkazivnika Vkazivnik na B2 f2 znahoditsya poza mezhami virtualnoyi tablici dlya D Dlya porivnyannya viklik d gt f0 znachno prostishij B1 f0 d EfektivnistVirtualnij viklik vimagaye shonajmenshe dodatkovogo indeksovanogo rozimenuvannya ta inodi koriguvannya vkazivnika porivnyano z nevirtualnim viklikom yakij ye prostim perehodom za skompilovanim vkazivnikom Cherez ce viklik virtualnih funkcij znachno povilnishij za viklik nevirtualnih Eksperimenti pokazuyut sho blizko 6 13 chasu vikonannya pripadaye na perehid do korektnoyi funkciyi hocha verhnya mezha dosyagaye 50 Viklik virtualnih funkcij mozhe buti ne takim dorogim na procesorah iz suchasnoyu arhitekturoyu zavdyaki znachno bilshomu keshu i krashomu peredbachennyu perehodiv U seredovishah de ne vikoristovuyetsya JIT kompilyaciya vikliki virtualnih funkcij ne mozhut buti angl inline Kompilyator mozhe zaminiti poshuk i nepryamij viklik na napriklad umovne vikonannya kozhnogo tila funkciyi ale taka optimizaciya nerozpovsyudzhena Dlya uniknennya dodatkovih dij kompilyatori zazvichaj unikayut vikoristannya tablici virtualnih funkcij koli viklik mozhe buti rozv yazanim pid chas kompilyaciyi Takim chinom poperednij viklik f1 mozhe ne vimagati pereglyadu virtualnoyi tablici cherez te sho kompilyator mozhe viznachiti shod v danomu vipadku mozhe mistiti tilki D i D ne zamishuye f1 Abo kompilyator chi optimizator mozhe buti v zmozi viznachiti sho v programi nemaye pidklasiv B1 yaki zamishayut f1 Viklik B1 f1 abo B2 f2 mozhlivo ne budut vimagati pereglyadu virtualnoyi tablici cherez yavno viznachenu realizaciyu hocha koriguvannya this vkazivnika vse she potribno Porivnyannya z alternativamiVirtualna tablicya zazvichaj ye kompromisom z dosit horoshoyu virobnistyu dlya dosyagnennya dinamichnoyi dispetcherizaciyi ale isnuyut alternativi taki yak dispetcherizaciya za dvijkovim derevom z bilshoyu virobnistyu ale riznoyu shvidkistyu vikonannya Odnak virtualni tablici dozvolyayut odinichnu dispetcherizaciyu angl single dispatch za specialnim parametrom this na vidminu vid mnozhinnoyi dispetcherizaciyi yak v CLOS abo Dylan de tip parametra beretsya do uvagi pid chas dispetcherizaciyi Virtualni tablici pracyuyut tilki koli dispetcherizaciya obmezhena vidomim naborom metodiv tobto voni mozhut buti rozmisheni v prostih masivah stvorenih pid chas kompilyaciyi na vidminu vid mov z kachinoyu tipizaciyeyu angl Duck typing takih yak Smalltalk Python abo JavaScript Movi sho pidtrimuyut odin abo obidva cih metodi chasto zdijsnyuyut dispetcherizaciyu cherez poshuk ryadka v hesh tablici abo inshim podibnim metodom Isnuye bagato vivertiv yaki dozvolyayut prishvidshiti ce napriklad tokenizaciya imen metodiv keshuvannya rezultativ poshuku JIT kompilyaciya i chas dispetcherizaciyi nemaye znachnogo vplivu na zagalnij chas vikonannya prote vikoristannya virtualnih tablic vochevid shvidshe Yih takozh legshe realizovuvati i znevadzhuvati takozh voni blizhche do C stilyu nizh hesh tablici ryadkiv Zauvazhimo sho Objective C 2 1 pidtrimuye dispetcherizaciyi za dopomogoyu virtualnih tablic pid 64 bitovoyu Mac OS X 10 6 PrimitkiEllis amp Stroustrup 1990 pp 227 232 Arhiv originalu za 27 listopada 2012 Procitovano 11 veresnya 2010 Driesen Karel and Holzle Urs The Direct Cost of Virtual Function Calls in C 2017 08 10 u Wayback Machine OOPSLA 1996 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 http arstechnica com apple reviews 2009 08 mac os x 10 6 ars 5 G s fdump class hierarchy argument mozhe buti vikoristanij z metoyu vivantazhennya tablici virtualnih metodiv dlya ruchnoyi perevirki U vipadku kompilyatora AIX VisualAge XlC vikoristovujte qdump class hierarchy dlya vivantazhennya iyerarhiyi klasiv i rozmishennya virtualnoyi tablici