[ Goran Arandjelovic @ 27.07.2005. 21:48 ] @
Da li neko ukratko moze da mi objasni kako funkcionisu VTABLES? Mislim, lepa je ova knjiga Thinking in C++ :) ali je mnogo... :) |
[ Goran Arandjelovic @ 27.07.2005. 21:48 ] @
[ Dragi Tata @ 28.07.2005. 02:45 ] @
[ Goran Arandjelovic @ 29.07.2005. 15:54 ] @
Da, to sam znao... nego, mislio sam da se pojam "V-table" odnosi na nesto drugo, ali koliko sam shavtio to je samo sematski prikaz virtualnih funkcija...
[ erno @ 01.08.2005. 09:13 ] @
pozdrav,
e pa u V-tabelama su sacuvani svi tzv. V-pointeri koji pokazuju na virtualne funkcije. a zasto su usopte virtualne funkcije? ok, jedna od primjena je recimo da imas osnovnu klasu Zivotinja i iz te klase su izvedene milion drugih, recimo 5 :-), u svakoj od tih klasa imas jednu overrided funkciju. ako te funkcije nisu definisane kao virtualne, onda ce se uvijek javljati funkcija iz osnovne klase ako imamo: Zivotinja *nova = new Slon; u ovom slucaju klasa Slon je izvedena iz osnovne klase Zivotinja te je naslijedila sva svojstva te klase. sta hocu da kazem? iz osnovne klase uvijek mozes dobiti izvedne klase kao sto sam ja to uradio gore. npr. Zivotinja *nova = new Vuk; u ovom slucaju stvorio si objekt tipa Vuk ali ako pozoves tu neku overrided funkciju pokrenuce se ona iz osnovne klase. da bi to izbjegli, tj. da bi se pokretala ona prava funkcija koriste se virtual funkcije koje imaju svoje virtualne tabele (grubo receno) u kojima su virtualni pointeri koji opet pokazuju na tu pravu funkciju. nadam se da ti je pomoglo posto bas i nisam najbolji u objasnjavanju. pozdrav, erno [ Goran Arandjelovic @ 01.08.2005. 19:58 ] @
Citat: e pa u V-tabelama su sacuvani svi tzv. V-pointeri koji pokazuju na virtualne funkcije. ok, to je ono sto mi je trebalo, a ostalo mi je bilo sasvim jasno (mehanizam v f-ja) da li se ikako moze 'pristupiti' toj tabeli ili je to nesto fiktivno? [ leka @ 12.08.2005. 01:33 ] @
VTables se koriste samo u slucaju da postoji nekakvo nasledjivanje. Dakle odmah covek moze da shvati da bilo kakvo nasledjivanje klasa odmah znaci (kakav-takav) overhead.
Uzmimo da imamo dve klase Mama i Dete: Code: class Mama { public: virtual void nesto() = 0; virtual void ime() { printf("Mama\n"); } Mama(int argGodina, int argDece = 1); virtual ~Mama(); private: int _godina; int _kolikoDece; }; class Dete { public: virtual void nesto(); Dete(int argGodina, bool jedino = false); virtual ~Dete(); private: bool _jedino; }; Ovaj kod se otprilike u C-u "pise" ovako (imajte na umu da nije ceo kod u pitanju): Code: typedef int (*VirtualFunctionPointer)(); struct VTable { int i; int d; VirtualFunctionPointer vtableFuncPtr; }; struct Mama { int _godina; int _kolikoDece; // pokazivac na vtable VTable* vtablePtr; }; struct Dete { // Nasledjeno od Mama tipa: int _godina; int _kolikoDece; // Clanovi dodani od strane "Dete" tipa: bool _jedino; // pokazivac na virtualnu funkciju VTable* vtablePtr; }; Kao sto se vidi svaka struktura koja predstavlja istoimenu klasu u prethodnom kodu poseduje odgovarajuce clanove i OBAVEZNO pokazivac na VTable... U ovom konkretnom primeru, kako izgleda taj famozni VTable? Pokusacu da sto pribliznije (mada ni ja ne shvatam sve u potpunosti, tako da - ne uzimajte ovo zdravo-za-gotovo) objasnim: Code: VTable vtableMama[] = { // nesto() je cisti virtuelni metod, te se ne sme direktno zvati // zato pozovi pvc_error_handler() funkciju koja ce odmah da prijavi gresku. { 0, 0, pvc_error_handler }, // ime() metod { 0, 0, Mama_ime }, // ~Mama() { 0, 0, Mama_destructor} }; VTable vtableDete[] = { // nesto() je virtuelni, ali konkretni, metod { 0, 0, Dete_nesto }, // Poziva bazni ime() metod, jer u ovoj klasi nije definisan. { 0, 0, Mama_ime }, // ~Dete() { 0, 0, Dete_destructor} }; Okej, imamo v-table. I sta sad? Pa nista bez konstruktora naravno. :) Code: Mama* Mama_constructor(Mama* this, int argGodina, int argDece = 1) { if (this == 0) { // Ako memorija nije alocirana za strukturu Mama: this = malloc(sizeof(Mama)); } if (this) { // mozda malloc() ipak nije uspeo... // inicijalizuj VTable pokazivac vtablePtr. this->vtablePtr = vtableMama; this->_godina = argGodina; this->_kolikoDece = argDece; } return this; } void Mama_destructor(Mama* this, bool dynamic) { // Ponovo setujemo vtablePtr pointer, jer nije sigurno da on pokazuje na // "dobru" vrednost. /**** glavno telo destruktora ****/ this->vtablePtr = vtableMama; // Oslobadjamo memoriju SAMO ako je memorija alocirana eksplicitno od strane Mama tipa if (dynamic) free(this); } Dete* Dete_constructor(Dete* this, int argGodina, int argDece, int argJedino = 0) { if (this == 0) { // Ako memorija nije alocirana za strukturu Dete: this = malloc(sizeof(Dete)); } if (this) { // mozda malloc() ipak nije uspeo... // Pozovi prvo konstruktor bazne klase Mama_constructor((Mama*)this, argGodina, argDece); // inicijalizuj VTable pokazivac vtablePtr. this->_jedino = argJedino; } return this; } void Dete_destructor(Dete* this, bool dynamic) { // Ponovo inicijalizujemo vtablePtr pointer, jer nije sigurno da on pokazuje na // "dobru" vrednost. this->vtablePtr = vtableDete; /**** glavno telo destruktora ****/ // Pozivamo destriktor baznog tipa - Mama Mama_destructor((Mama*) this, false); // Oslobadjamo memoriju SAMO ako je memorija alocirana eksplicitno od strane Dete tipa if (dynamic) free(this); } Nema potrebe da pisem kod za ostale metode, ali ima potrebe da objasnim jos jednu stvar u vezi VTables, a to je - kako se koriste podaci iz njih. Tu dolazimo do main() funcije... Code: /* ukratko, ipak je 2.11AM ... */ int main() { // Kreiramo objekat tipa Dete, nema smisla kreirati objekat tipa Mama (apstraktna klasa). // S obzirom da je prvi argument (this) NULL onda ce se memorija alocirati... Mama* mamaPtr = Dete_constructor(NULL, 40, 2, true); Dete objekatDete; Dete_constructor(&objekatDete, 12, 0, 1); // Pozovimo virtuelni metod nesto() // Ovaj red je isto sto i: mamaPtr->nesto(); (mamaPtr->vtablePtr[0].vtableFuncPtr)(mamaPtr); // Sledeci red je isto sto i mamaPtr->ime(); (mamaPtr->vtablePtr[1].vtableFuncPtr)(mamaPtr); // Recimo da imamo metod imePrezime(str argIme, str argPrezime) // i da imamo negde dve promenljive str1 i str2 tipa str (#define std::string str recimo :) // I da je ova funkcija u VTables na mestu sa indeksom 5, onda bi je pozvali sa: (mamaPtr->vtablePtr[5].vtableFuncPtr)(mamaPtr, "Marina", "Perazic"); // C++: delete objekatDete Dete_destructor(&objekatDete, false); return 0; } Dete_destructor linija treba da se pojasni... Naime, memorija je alocirana sa steka za "objekatDete", tako da nema smisla pozivati destructor sa argumentom true, jer ce ta varijabla svejedno da se obrise kada objekatDete izadje iz oblasti vazenja, u ovom slucaju kad se izadje iz funkcije main(). No, ovaj destruktor poziva destruktor bazne klase, te se tako sve lepo ocisti... Igrom slucaja radim binding jedne C++ biblioteke za jedan maleni interpreter, te je trebalo da "flatten"-ujem C++ klase u C strukture, sa jos par zavrzlama, te otprilike znam kako interno C++ radi sa v-tables. Jos mnogo toga nije jasno, ali stvari lagano dolaze na svoje mesto i sve postaje jako jednostavno i prosto kada se ovo gore shvati. [ tosa @ 12.08.2005. 06:55 ] @
Jos kad bi pointeri na member funkcije bili iste velicine kao "normalni" pointeri,
gde bi nam bio kraj! [ Dragi Tata @ 12.08.2005. 13:12 ] @
Citat: Leka: Dakle odmah covek moze da shvati da bilo kakvo nasledjivanje klasa odmah znaci (kakav-takav) overhead. Možda sa GCC-om, ali iole pametni kompajleri jednostavno izbace V-table ako se objekat ne koristi polimorfno. [ Branimir Maksimovic @ 12.08.2005. 19:21 ] @
Citat: Dragi Tata: Možda sa GCC-om, ali iole pametni kompajleri jednostavno izbace V-table ako se objekat ne koristi polimorfno. Ne moze kompajler da izbaci vtable jer on nema uvida u to ko ce sve koristiti objekt van kompilacione jedinice. To moze jedino linker da stripuje ako je uopste moguce jer sam layout objekta uveliko zavisi od toga da li postoje virtuelne funkcije. Ono sto svi kompajleri rade je da call koji se ne dispatch-uje virtuelno, ne izvode kroz vtable nego direktno. Potom, vtable se ne koristi samo za virtuelne funkcije vec i za RTTI. Citat: tosa: Jos kad bi pointeri na member funkcije bili iste velicine kao "normalni" pointeri, gde bi nam bio kraj! Tesko da mogu sa obzirom da oni mogu drzati dve razlicite informacije: ofset u vtable ili pointer na funckiju. Znaci samo adresa/ofset nije dovoljna informacija, treba i informacija o tome sta pointer predstavlja u datom trenutku. Citat: leka: VTables se koriste samo u slucaju da postoji nekakvo nasledjivanje. Dakle odmah covek moze da shvati da bilo kakvo nasledjivanje klasa odmah znaci (kakav-takav) overhead. vtable nije obavezna implementacija mehanizma virtuelnih funkcija, eto recimo smarteiffel ne koristi vtable. Drugo, nasledjivanje ne uslovljava vtable uopste, nego postojanje bar jedne virtuelne funkcije. U tom slucaju se pravi vtable za datu klasu, bilo nasledjivanja ili ne. Citat: leka: Igrom slucaja radim binding jedne C++ biblioteke za jedan maleni interpreter, te je trebalo da "flatten"-ujem C++ klase u C strukture, sa jos par zavrzlama, te otprilike znam kako interno C++ radi sa v-tables. Jos mnogo toga nije jasno, ali stvari lagano dolaze na svoje mesto i sve postaje jako jednostavno i prosto kada se ovo gore shvati. Ovo sto radis je sizifov posao, jer sam layout objekta i tabele se menja od kompajlera do kompajlera. Recimo gcc 2.x.x je drugaciji od gcc 3.x.x, a 4.x.x nisam ni gledao, tako da moras da vezujes kod za verziju/kompajler koji koristis. Pozdrav svima, Bane. [ leka @ 12.08.2005. 23:04 ] @
Citat: Možda sa GCC-om, ali iole pametni kompajleri jednostavno izbace V-table ako se objekat ne koristi polimorfno. Nemanja, pogresno si me shvatio - ja sam mislio reci da je overhead svaka vrsta polimorfizma. Sto se tice "Možda sa GCC-om" - to necu da komentarisem, nek ide na savest onoga ko je tako nesto izjavio. [Ovu poruku je menjao leka dana 13.08.2005. u 00:06 GMT+1] [ Dragi Tata @ 13.08.2005. 01:50 ] @
Citat: leka: Nemanja, pogresno si me shvatio - ja sam mislio reci da je overhead svaka vrsta polimorfizma. Pa kako se uzme. Overhead u odnosu na šta? Bez virtuenih funkcija bi morao da držiš podatke o tipu objekta plus switch-case u svakoj "polimorfnoj" situaciji - izađe mu na isto. Citat: leka: Sto se tice "Možda sa GCC-om" - to necu da komentarisem, nek ide na savest onoga ko je tako nesto izjavio. Hehehe, ne daj se isprovocirati, Leko, legendo ;) [ Dragi Tata @ 13.08.2005. 01:57 ] @
Citat: Branimir Maksimovic: Ne moze kompajler da izbaci vtable jer on nema uvida u to ko ce sve koristiti objekt van kompilacione jedinice. Jeste, jeste, u stvari sam mislio na pointere na VT, a ne samu VT koja je jedna po klasi. Nego, da nisi ti ovaj: http://groups-beta.google.com/...m=3&hl=en#29404615dc5dfbab Copyright (C) 2001-2025 by www.elitesecurity.org. All rights reserved.
|