Virtuaalinen menetelmätaulukko ( VMT) - koordinointitaulukko tai vtable - mekanismi, jota käytetään ohjelmointikielissä tukemaan dynaamista sovitusta (tai myöhäistä sidontamenetelmää).
Oletetaan, että ohjelma sisältää useita luokkia periytymishierarkiassa: perusluokan ja Cat kaksi alaluokkaa DomesticCat ja Lion. Luokka Catmäärittelee virtuaalisen funktion speak , jotta sen alaluokat voivat tarjota sopivan toteutuksen (eli "miau" tai "karjuu").
Kun ohjelma kutsuu menetelmää speakosoittimella Cat(joka voi osoittaa luokkaan Cattai mihin tahansa alaluokkaan Cat), kontekstiympäristön (ajonaikaisen ympäristön) on kyettävä määrittämään, mitä toteutusta kutsutaan, riippuen osoitetun objektin nykyisestä tyypistä.
Tällaista dynaamista linkittämistä on monia eri tapoja toteuttaa, mutta virtuaalitaulukkoratkaisu on melko yleinen C++ :ssa ja siihen liittyvissä kielissä (kuten D ja C# ). Kielet, joissa on ero kohteen API:n ja sen toteutuksen välillä, kuten Visual Basic ja Delphi , käyttävät myös yleensä virtuaalitaulukkoanalogeja, koska tämä antaa objektille mahdollisuuden käyttää eri toteutusta yksinkertaisesti käyttämällä erilaista menetelmäosoittimia.
Objektin koordinointitaulukko sisältää objektin dynaamisesti linkitettyjen menetelmien osoitteet. Metodia kutsutaan, kun menetelmän osoite haetaan taulukosta. Koordinointitaulukko on sama kaikille samaan luokkaan kuuluville objekteille, joten jakaminen on sallittua. Objekteilla, jotka kuuluvat tyyppiyhteensopiviin luokkiin (esimerkiksi ne, jotka ovat samalla tasolla periytymishierarkiassa), on samanlaiset koordinointitaulukot: tietyn menetelmän osoite kiinnitetään samaan siirtymään kaikille tyyppiyhteensopiville luokille. Siten valitsemalla menetelmän osoitteen annetusta koordinointitaulukosta poikkeamalla, saamme objektin nykyiseen luokkaan liittyvän menetelmän. [yksi]
C++-standardit eivät määrittele selkeästi, kuinka dynaaminen koordinointi tulisi toteuttaa, mutta kääntäjät käyttävät usein jotakin saman perusmallin muunnelmaa.
Yleensä kääntäjä luo jokaiselle luokalle erillisen virtuaalitaulukon. Kun objekti on luotu, osoitin kyseiseen virtuaalitaulukkoon, jota kutsutaan virtuaalitaulukon osoittimeksi tai vptr- osoittimeksi (kutsutaan myös joskus vptr- tai vfptr-osoittimeksi), lisätään kyseisen objektin piilotettuna jäsenenä (ja usein ensimmäisenä jäsenenä). Kääntäjä luo myös "piilotetun" koodin kunkin luokan rakentajaan alustaakseen objektin v-osoittimet vastaavan vtaulukon osoitteilla.
Harkitse seuraavia luokkamäärityksiä C++:ssa:
luokka B1 { julkinen : void f0 () {} virtuaalinen void f1 () {} int int_in_b1 ; }; luokka B2 { julkinen : virtuaalinen void f2 () {} int int_in_b2 ; };käytä seuraavan luokan luomiseen:
luokka D : julkinen B1 , julkinen B2 { julkinen : mitätön d () {} void f2 () {} // ohittaa B2::f2() int int_in_d ; };ja seuraava C++-koodinpätkä:
B2 * b2 = uusi B2 (); D * d = uusi D ();G++ 3.4.6 GCC -sarjasta luo seuraavan 32-bittisen muistikartan objektille b2 (здесь и далее ТВМ - таблица виртуальных методов): [nb 1]
b2: +0: osoitin TVM B2:een +4: arvo int_in_b2 TVM B2: +0:B2::f2()ja objektin dmuistikaavio on seuraava:
d: +0: osoitin TVM D:lle (B1) +4: arvo int_in_b1 +8: osoitin TVM D:lle (B2) +12: arvo int_in_b2 +16: int_in_d arvo Kokonaiskoko: 20 tavua. TVM D (B1): +0: B1::f1() // B1::f1() ei ole määritelty uudelleen TVM D (B2): +0:D::f2() // B2::f2() korvattu D::f2()On huomattava, että ei-virtuaaliset funktiot (kuten f0) eivät yleensä voi esiintyä virtuaalitaulukossa, mutta joissakin tapauksissa on poikkeuksia (kuten oletuskonstruktori).
Metodin f2()uudelleenmäärittely luokassa Dtoteutetaan monistamalla TCM B2ja korvaamalla B2::f2()osoitin kohtaan D::f2().
Luokkien moninkertainen periminen luokasta B1ja B2luokasta Dkäyttämällä kahta virtuaalista menetelmätaulukkoa, yksi jokaiselle perusluokalle. (On olemassa muita tapoja toteuttaa useita perintöjä, mutta tämä on yleisin.) Tämä johtaa siihen, että luomisen yhteydessä tarvitaan " osoittimia osoitetietueen osoittamiseen " (sidontoja).
Harkitse seuraavaa C++-koodia:
D * d = uusi D (); B1 * b1 = dynaaminen_lähetys < B1 *> ( d ); B2 * b2 = dynaaminen_lähetys < B2 *> ( d );Vaikka dja b1osoita yhteen paikkaan muistissa tämän koodin suorittamisen jälkeen, b2osoittavat muistipaikkaa d+8(kahdeksan tavun siirtymä sijainnista d). Siten b2viittaa muistin alueeseen sisällä d, joka "näyttää" entiteetiltä B2, ts. on sama muistiasettelu kuin entiteetillä B2.
Kutsu d->f1()tapahtuu, kun v-osoittimeen viitataan D::B1osoitteesta d: etsitään o-merkintä f1virtuaalitaulukosta ja tämän osoittimen viittauksen poistaminen kutsuu koodia.
Jos kyseessä on yksittäinen periytyminen (tai kieli, joka tukee vain yksittäistä periytymistä), jos vpointer on aina ensimmäinen elementti d(kuten monien kääntäjien tapauksessa), tämä ratkaistaan seuraavalla pseudo-C++-koodilla :
* (( * d )[ 0 ])( d )Yleisemmässä tapauksessa, kuten edellä mainittiin, soittaminen ja f1()soittaminen on vaikeampaa D::f2()B2::f2()d
* (( d -> /*TBM-osoitin D (B1:lle)*/ )[ 0 ])( d ) // d->f1(); * (( d -> /*TBM-osoitin D (B2:lle)*/ )[ 0 ])( d + 8 ) // d->f2(); * (( /* TVM B2:n osoite */ )[ 0 ])( d + 8 ) // d->B2::f2();Siihen verrattuna puhelu d->f0()on paljon yksinkertaisempi:
* B1 :: f0 ( d )Virtuaalinen puhelu vaatii ainakin ylimääräisen indeksoidun viittauksen ja joskus ylimääräisen "korjauksen", joka on samanlainen kuin ei-virtuaalinen puhelu, joka on yksinkertainen hyppy koottua osoitinta. Siksi virtuaalisten funktioiden kutsuminen on luonnostaan hitaampaa kuin ei-virtuaalisten funktioiden kutsuminen. Vuonna 1996 suoritettu koe osoitti, että noin 6-13 % suoritusajasta kuluu yksinkertaisesti sopivan toiminnon etsimiseen, kun taas suoritusajan kokonaislisäys voi olla 50 % [2] . Virtuaalitoimintojen käyttökustannukset nykyaikaisissa prosessoriarkkitehtuureissa eivät välttämättä ole yhtä korkeat, koska välimuistit ovat paljon suurempia ja haarojen ennakointi on parempi .
Ympäristössä, jossa JIT - kääntämistä ei käytetä, virtuaaliset funktiokutsut eivät yleensä voi olla sisäisiä . Vaikka kääntäjä voi korvata haun ja epäsuoran kutsun esimerkiksi suorittamalla ehdollisesti jokaisen sisäisen rungon, tällainen optimointi ei ole yleinen.
Tällaisen tuhlauksen välttämiseksi kääntäjät yleensä välttävät virtuaalisten taulukoiden käyttöä aina, kun käännösaikana voidaan soittaa puhelu.
Näin ollen yllä oleva kutsu f1ei välttämättä vaadi virtuaalitaulukon hakua, koska kääntäjä voi raportoida vain sen, mitä sillä dvoi tuossa vaiheessa olla D, sen sijaan, että määrittäisi sen Duudelleen f1. Tai kääntäjä (tai vaihtoehtoisesti optimoija) saattaa havaita alaluokkien puuttumisen B1ohjelmassa, joka ohittaa f1. Kutsuminen B1::f1tai B2::f2ei todennäköisesti vaadi virtuaalitaulukon hakua nimenomaisen toteutuksen vuoksi (vaikka "tämän" osoittimen sitominen vaaditaan silti).
Virtuaalitaulukko yleensä uhraa suorituskyvyn dynaamisen valinnan saavuttamiseksi, mutta sille on monia vaihtoehtoja, kuten binääripuuvalinta, jolla on parempi suorituskyky, mutta erilaiset suoritusnopeudet [3] .
Virtuaalitaulukko tarjotaan kuitenkin vain yksittäistä lähetystä varten erityisellä "this"-parametrilla, toisin kuin useissa lähetyksissä (kuten CLOSissa tai Dylanissa ), jossa kaikkien parametrien tyypit voidaan määrittää lähetyksen aikana.
Virtuaalinen taulukko toimii myös vain, jos lähetys on rajoitettu tunnettuun joukkoon menetelmiä, joten monet virtuaalitaulukot voidaan laittaa yksinkertaiseen taulukkoon käännösvaiheessa, toisin kuin ankan kirjoittamista tukevat kielet (kuten Smalltalk , Python tai JavaScript ).
Kielet, jotka tukevat jompaakumpaa tai molempia näistä vaihtoehdoista, lähetetään usein etsimällä merkkijono hash-taulukosta tai muulla vastaavalla menetelmällä. Nopeuden parantamiseksi on olemassa useita temppuja (esim. menetelmien nimien tokenointi, välimuistin käyttäminen, JIT - kääntäminen), eikä lähetysajalla useinkaan ole merkittävää vaikutusta kokonaiskäsittelyaikaan, mutta tästä huolimatta virtuaalitaulukoiden haut ovat huomattavasti nopeampia . . Virtuaalitaulukko on myös helpompi toteuttaa ja virheenkorjaus, ja se on myös lähempänä "C-filosofiaa" kuin string hash tables -linkki? .