runtime polymorphism c
Podrobná štúdia runtime polymorfizmu v C ++.
Runtime polymorfizmus je tiež známy ako dynamický polymorfizmus alebo neskorá väzba. V runtime polymorfizme sa volanie funkcie rieši za behu programu.
Naproti tomu na kompiláciu času alebo statického polymorfizmu kompilátor odvodí objekt za behu a potom rozhodne, ktoré volanie funkcie sa má viazať na objekt. V C ++ je runtime polymorfizmus implementovaný pomocou prepísania metódy.
V tomto výučbe podrobne preskúmame všetko o runtime polymorfizme.
=> Skontrolujte VŠETKY výukové programy pre C ++ tu.
Čo sa dozviete:
- Prepísanie funkcie
- Virtuálna funkcia
- Pracovanie s virtuálnou tabuľkou a _vptr
- Čisté virtuálne funkcie a abstraktná trieda
- Virtuálne ničitelia
- Záver
- Odporúčané čítanie
Prepísanie funkcie
Prepísanie funkcie je mechanizmus, pomocou ktorého je funkcia definovaná v základnej triede opäť definovaná v odvodenej triede. V tomto prípade hovoríme, že funkcia je v odvodenej triede prepísaná.
Mali by sme pamätať na to, že prepísanie funkcie nie je možné vykonať v rámci triedy. Funkcia je prepísaná iba v odvodenej triede. Preto by malo byť prítomné dedičstvo pre prvoradú funkciu.
Druhá vec je, že funkcia zo základnej triedy, ktorú prekonávame, by mala mať rovnaký podpis alebo prototyp, t. J. Mala by mať rovnaký názov, rovnaký návratový typ a rovnaký zoznam argumentov.
Pozrime sa na príklad, ktorý demonštruje prepísanie metódy.
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'< Výkon:
Trieda :: Základňa
Trieda :: Odvodené
Vo vyššie uvedenom programe máme základnú triedu a odvodenú triedu. V základnej triede máme funkciu show_val, ktorá je v odvodenej triede prepísaná. V hlavnej funkcii vytvoríme objekt každej z tried Base a Derived a s každým objektom zavoláme funkciu show_val. Produkuje požadovaný výstup.
Vyššie uvedená väzba funkcií pomocou objektov každej triedy je príkladom statickej väzby.
Teraz sa pozrime, čo sa stane, keď použijeme ukazovateľ základnej triedy a ako obsah priradíme objekty odvodenej triedy.
Príklad programu je uvedený nižšie:
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'; } }; class Derived:public Base { public: void show_val() //overridden function { cout <<'Class::Derived'; } }; int main() { Base* b; //Base class pointer Derived d; //Derived class object b = &d; b->show_val(); //Early Binding }
Výkon:
Trieda :: Základňa
Teraz vidíme, že výstup je „Class :: Base“. Takže bez ohľadu na to, aký typ objektu drží základný ukazovateľ, program vypíše obsah funkcie triedy, ktorej základný ukazovateľ je typom. V tomto prípade sa vykoná aj statické prepojenie.
Aby bol výstup základného ukazovateľa správny obsah a správne prepojenie, ideme na dynamické viazanie funkcií. Toho sa dosahuje pomocou mechanizmu virtuálnych funkcií, ktorý je vysvetlený v nasledujúcej časti.
Virtuálna funkcia
Pretože prepísaná funkcia by mala byť dynamicky viazaná na telo funkcie, urobíme funkciu základnej triedy virtuálnou pomocou kľúčového slova „virtual“. Táto virtuálna funkcia je funkcia, ktorá je prepísaná v odvodenej triede a kompilátor vykoná pre túto funkciu neskoré alebo dynamické viazanie.
Teraz upravíme vyššie uvedený program tak, aby obsahoval virtuálne kľúčové slovo, a to nasledovne:
#include using namespace std;. class Base { public: virtual void show_val() { cout << 'Class::Base'; } }; class Derived:public Base { public: void show_val() { cout <<'Class::Derived'; } }; int main() { Base* b; //Base class pointer Derived d; //Derived class object b = &d; b->show_val(); //late Binding }
Výkon:
Trieda :: Odvodené
aké programy používajú c ++
Takže vo vyššie uvedenej definícii triedy Base sme vytvorili funkciu show_val ako „virtuálnu“. Pretože sa funkcia základnej triedy stáva virtuálnou, keď priradíme objekt odvodenej triedy k ukazovateľu základnej triedy a zavoláme funkciu show_val, k väzbe dôjde za behu modulu.
Pretože ukazovateľ základnej triedy obsahuje objekt odvodenej triedy, telo funkcie show_val v odvodenej triede je viazané na funkciu show_val a teda na výstup.
V C ++ môže byť prepísaná funkcia v odvodenej triede tiež súkromná. Kompilátor kontroluje iba typ objektu v čase kompilácie a viaže funkciu za behu, takže to nerobí žiadny rozdiel, aj keď je funkcia verejná alebo súkromná.
Upozorňujeme, že ak je funkcia v základnej triede deklarovaná ako virtuálna, bude virtuálna vo všetkých odvodených triedach.
Ale doteraz sme nerozoberali, ako presne majú virtuálne funkcie úlohu pri identifikácii správnej funkcie, ktorá sa má viazať, alebo inými slovami, ako neskoro sa väzba vlastne deje.
Virtuálna funkcia je za behu programu presne zviazaná s telom funkcie pomocou konceptu virtuálna tabuľka (VTABLE) a zavolal skrytý ukazovateľ _vptr.
Oba tieto koncepty sú internej implementácie a program ich nemôže priamo použiť.
Pracovanie s virtuálnou tabuľkou a _vptr
Najprv pochopíme, čo je to virtuálna tabuľka (VTABLE).
Kompilátor v čase kompilácie nastaví po jednej VTABLE pre triedu s virtuálnymi funkciami, ako aj pre triedy, ktoré sú odvodené od tried s virtuálnymi funkciami.
A VTABLE obsahuje položky, ktoré sú ukazovateľmi funkcií na virtuálne funkcie, ktoré môžu byť volané objektmi triedy. Pre každú virtuálnu funkciu existuje jeden vstup ukazovateľa funkcie.
V prípade čisto virtuálnych funkcií je táto položka NULL. (To je dôvod, prečo nemôžeme vytvoriť inštanciu abstraktnej triedy).
Ďalšou entitou, _vptr, ktorá sa nazýva ukazovateľ vtable, je skrytý ukazovateľ, ktorý kompilátor pridá do základnej triedy. Tento _vptr ukazuje na vtable triedy. Všetky triedy odvodené z tejto základnej triedy dedia _vptr.
Každý objekt triedy obsahujúci virtuálne funkcie interne ukladá tento _vptr a je pre používateľa transparentný. Každé volanie virtuálnej funkcie pomocou objektu sa potom vyrieši pomocou tohto _vptr.
Zoberme si príklad na demonštráciu fungovania vtable a _vtr.
#include using namespace std; class Base_virtual { public: virtual void function1_virtual() {cout<<'Base :: function1_virtual()
';}; virtual void function2_virtual() {cout<<'Base :: function2_virtual()
';}; virtual ~Base_virtual(){}; }; class Derived1_virtual: public Base_virtual { public: ~Derived1_virtual(){}; virtual void function1_virtual() { coutfunction2_virtual(); delete (b); return (0); }
Výkon:
Derived1_virtual :: function1_virtual ()
Base :: function2_virtual ()
Vo vyššie uvedenom programe máme základnú triedu s dvoma virtuálnymi funkciami a virtuálnym deštruktorom. Taktiež sme odvodili triedu od základnej triedy; prepísali sme iba jednu virtuálnu funkciu. V hlavnej funkcii je odvodený ukazovateľ triedy priradený základnému ukazovateľu.
Potom zavoláme obe virtuálne funkcie pomocou ukazovateľa základnej triedy. Vidíme, že prepísaná funkcia sa volá, keď sa volá, a nie základná funkcia. Zatiaľ čo v druhom prípade sa funkcia neprepíše, nazýva sa funkcia základnej triedy.
Teraz sa pozrime, ako je vyššie uvedený program reprezentovaný interne pomocou nástrojov vtable a _vptr.
Podľa predchádzajúceho vysvetlenia, keďže existujú dve triedy s virtuálnymi funkciami, budeme mať dve tabuľky - jednu pre každú triedu. Pre základnú triedu bude prítomný aj _vptr.

Vyššie je zobrazené obrazové znázornenie toho, aké bude rozloženie vtable pre vyššie uvedený program. Vtable pre základnú triedu je jednoduchý. V prípade odvodenej triedy je prepísaná iba funkcia1_virtual.
Preto vidíme, že v odvodenej triede vtable ukazuje ukazovateľ funkcie pre funkciu1_virtual na prepísanú funkciu v odvodenej triede. Na druhej strane ukazovateľ funkcie pre function2_virtual ukazuje na funkciu v základnej triede.
Takže vo vyššie uvedenom programe, keď je základnému ukazovateľu priradený objekt odvodenej triedy, základný ukazovateľ ukazuje na _vptr odvodenej triedy.
Takže keď sa uskutoční volanie b-> function1_virtual (), zavolá sa function1_virtual z odvodenej triedy a keď sa uskutoční volanie funkcie b-> function2_virtual (), pretože tento ukazovateľ funkcie smeruje na funkciu základnej triedy, funkcia základnej triedy sa volá.
Čisté virtuálne funkcie a abstraktná trieda
Podrobnosti o virtuálnych funkciách v C ++ sme videli v našej predchádzajúcej časti. V C ++ môžeme definovať aj „ čisto virtuálna funkcia “, Ktorá sa zvyčajne rovná nule.
Čistá virtuálna funkcia je deklarovaná tak, ako je uvedené nižšie.
virtual return_type function_name(arg list) = 0;
Trieda, ktorá má aspoň jednu čisto virtuálnu funkciu, ktorá sa nazýva „ abstraktná trieda “. Abstraktnú triedu nikdy nemôžeme vytvoriť inštanciu, t. J. Nemôžeme vytvoriť objekt abstraktnej triedy.
Je to tak preto, lebo vieme, že pre každú virtuálnu funkciu sa robí záznam vo VTABLE (virtuálna tabuľka). Ale v prípade čisto virtuálnej funkcie je táto položka bez akejkoľvek adresy, čo ju robí neúplnou. Kompilátor teda neumožňuje vytvoriť objekt pre triedu s neúplným záznamom VTABLE.
To je dôvod, pre ktorý nemôžeme vytvoriť inštanciu abstraktnej triedy.
Nasledujúci príklad demonštruje čistú virtuálnu funkciu aj triedu Abstract.
#include using namespace std; class Base_abstract { public: virtual void print() = 0; // Pure Virtual Function }; class Derived_class:public Base_abstract { public: void print() { cout <<'Overriding pure virtual function in derived class
'; } }; int main() { // Base obj; //Compile Time Error Base_abstract *b; Derived_class d; b = &d; b->print(); }
Výkon:
Prepísanie čistej virtuálnej funkcie v odvodenej triede
Vo vyššie uvedenom programe máme triedu definovanú ako Base_abstract, ktorá obsahuje čisto virtuálnu funkciu, ktorá z nej robí abstraktnú triedu. Potom odvodíme triedu „Derived_class“ z Base_abstract a prepíšeme v nej čistý virtuálny tlač funkcií.
V hlavnej funkcii nie je komentovaný prvý riadok. Je to preto, že ak to odkomentujeme, kompilátor dá chybu, pretože nemôžeme vytvoriť objekt pre abstraktnú triedu.
Ale druhý riadok ďalej kód funguje. Môžeme úspešne vytvoriť ukazovateľ základnej triedy a potom mu priradíme objekt odvodenej triedy. Ďalej zavoláme funkciu tlače, ktorá vyprodukuje obsah funkcie tlače prepísaný v odvodenej triede.
Uveďme v skratke niekoľko charakteristík abstraktnej triedy:
- Nemôžeme vytvoriť inštanciu abstraktnej triedy.
- Abstraktná trieda obsahuje aspoň jednu čisto virtuálnu funkciu.
- Aj keď nemôžeme vytvoriť inštanciu abstraktnej triedy, vždy môžeme vytvoriť ukazovatele alebo odkazy na túto triedu.
- Abstraktná trieda môže mať nejakú implementáciu, napríklad vlastnosti a metódy, spolu s čisto virtuálnymi funkciami.
- Keď odvodíme triedu z abstraktnej triedy, odvodená trieda by mala prepísať všetky čisté virtuálne funkcie v abstraktnej triede. Ak to neurobilo, odvodená trieda bude tiež abstraktnou triedou.
Virtuálne ničitelia
Destruktory triedy možno vyhlásiť za virtuálne. Kedykoľvek urobíme upcast, tj. Priradíme objekt odvodenej triedy k ukazovateľu základnej triedy, obyčajné deštruktory môžu priniesť neprijateľné výsledky.
Napríklad,zvážte nasledujúce upcastovanie obyčajného ničiteľa.
#include using namespace std; class Base { public: ~Base() { cout << 'Base Class:: Destructor
'; } }; class Derived:public Base { public: ~Derived() { cout<< 'Derived class:: Destructor
'; } }; int main() { Base* b = new Derived; // Upcasting delete b; }
Výkon:
Základná trieda :: Destruktor
Vo vyššie uvedenom programe máme zdedenú odvodenú triedu od základnej triedy. V hlavnej časti priradíme objekt odvodenej triedy ukazovateľu základnej triedy.
V ideálnom prípade by mal byť deštruktor, ktorý sa volá, keď sa volá „delete b“, mal odvodiť z odvodenej triedy, ale z výstupu vidíme, že deštruktor základnej triedy sa nazýva ako ukazovateľ základnej triedy.
Z tohto dôvodu sa odvodený destruktor triedy nezavolá a objekt odvodenej triedy zostáva nedotknutý, čo vedie k úniku pamäte. Riešením je vytvoriť virtuálny konštruktor základnej triedy tak, aby ukazovateľ objektu smeroval na opravu deštruktora a vykonanie správneho zničenia objektov.
V nasledujúcom príklade je zobrazené použitie virtuálneho deštruktora.
#include using namespace std; class Base { public: virtual ~Base() { cout << 'Base Class:: Destructor
'; } }; class Derived:public Base { public: ~Derived() { cout<< 'Derived class:: Destructor
'; } }; int main() { Base* b = new Derived; // Upcasting delete b; }
Výkon:
Odvodená trieda :: Destruktor
Základná trieda :: Destruktor
Toto je rovnaký program ako predchádzajúci program, až na to, že sme pred deštruktor základnej triedy pridali virtuálne kľúčové slovo. Tým, že sme deštruktor základnej triedy vytvorili virtuálnym, sme dosiahli požadovaný výstup.
Vidíme, že keď priradíme objekt odvodenej triedy k ukazovateľu základnej triedy a potom odstránime ukazovateľ základnej triedy, deštruktory sa vyvolajú v opačnom poradí ako vytváranie objektov. To znamená, že najskôr sa zavolá deštruktor odvodenej triedy a objekt sa zničí a potom sa zničí objekt základnej triedy.
Poznámka: V C ++ nemôžu byť konštruktory nikdy virtuálne, pretože konštruktory sa podieľajú na konštrukcii a inicializácii objektov. Preto potrebujeme, aby boli všetci konštruktéri úplne vykonaní.
Záver
Runtime polymorfizmus sa implementuje pomocou prepísania metódy. To funguje dobre, keď voláme metódy s ich príslušnými objektmi. Ale keď máme ukazovateľ základnej triedy a voláme prepísané metódy pomocou ukazovateľa základnej triedy ukazujúceho na objekty odvodenej triedy, dôjde k neočakávaným výsledkom kvôli statickému prepojeniu.
Aby sme to prekonali, používame koncept virtuálnych funkcií. Vďaka internému znázorneniu vtables a _vptr nám virtuálne funkcie pomáhajú presne volať požadované funkcie. V tomto tutoriáli sme si pozreli podrobne runtime polymorfizmus používaný v C ++.
Týmto uzatvárame naše výukové programy o objektovo orientovanom programovaní v C ++. Dúfame, že tento tutoriál pomôže získať lepšie a dôkladné pochopenie koncepcií objektovo orientovaného programovania v C ++.
=> Navštívte tu a dozviete sa C ++ od nuly.
Odporúčané čítanie
- Polymorfizmus v C ++
- Dedenie v C ++
- Funkcie priateľov v C ++
- Triedy a objekty v C ++
- Používanie triedy výberu selénu na prácu s prvkami rozbaľovacej ponuky na webovej stránke - Výučba selénu č. 13
- Výukový program pre hlavné funkcie Pythonu s praktickými príkladmi
- Virtuálny stroj Java: Ako JVM pomáha pri spúšťaní aplikácií Java
- Ako nastaviť súbory skriptu LoadRunner VuGen a nastavenia runtime