runtime polymorphism c
Un studiu detaliat al polimorfismului Runtime în C ++.
Polimorfismul în timpul rulării este, de asemenea, cunoscut sub numele de polimorfism dinamic sau legare tardivă. În polimorfismul de execuție, apelul funcției este rezolvat în timpul rulării.
În schimb, pentru a compila timpul sau polimorfismul static, compilatorul deduce obiectul în timpul rulării și apoi decide ce funcție apelează să se lege de obiect. În C ++, polimorfismul de rulare este implementat folosind metoda suprascriere.
În acest tutorial, vom explora în detaliu totul despre polimorfismul în timpul rulării.
=> Verificați TOATE tutorialele C ++ aici.
Ce veți învăța:
- Anularea funcției
- Funcție virtuală
- Funcționarea tabelului virtual și _vptr
- Funcții virtuale pure și clasa abstractă
- Destructori virtuali
- Concluzie
- Lectură recomandată
Anularea funcției
Suprascrierea funcției este mecanismul prin care o funcție definită în clasa de bază este definită din nou în clasa derivată. În acest caz, spunem că funcția este anulată în clasa derivată.
Ar trebui să ne amintim că suprascrierea funcției nu poate fi făcută în cadrul unei clase. Funcția este suprascrisă numai în clasa derivată. Prin urmare, moștenirea ar trebui să fie prezentă pentru suprascrierea funcției.
spring mvc întrebări și răspunsuri la interviu pentru experți
Al doilea lucru este că funcția dintr-o clasă de bază pe care o substituim ar trebui să aibă aceeași semnătură sau prototip, adică ar trebui să aibă același nume, același tip de returnare și aceeași listă de argumente.
Să vedem un exemplu care demonstrează că metoda este suprascrisă.
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'< Ieșire:
Clasa :: Baza
Clasa :: Derivat
În programul de mai sus, avem o clasă de bază și o clasă derivată. În clasa de bază, avem o funcție show_val care este suprascrisă în clasa derivată. În funcția principală, creăm câte un obiect din fiecare clasă de bază și derivată și apelăm funcția show_val cu fiecare obiect. Produce ieșirea dorită.
Legarea de mai sus a funcțiilor folosind obiecte din fiecare clasă este un exemplu de legare statică.
Acum, să vedem ce se întâmplă când folosim indicatorul clasei de bază și atribuim obiecte derivate ale clasei ca conținut.
Programul de exemplu este prezentat mai jos:
#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 }
Ieșire:
Clasa :: Baza
Acum vedem că ieșirea este „Class :: Base”. Deci, indiferent de ce tip de obiect ține pointerul de bază, programul afișează conținutul funcției clasei al cărui pointer de bază este tipul. În acest caz, se realizează și legarea statică.
Pentru a face ieșirea pointerului de bază, conținutul corect și legarea corectă, mergem pentru legarea dinamică a funcțiilor. Acest lucru se realizează utilizând mecanismul funcțiilor virtuale, care este explicat în secțiunea următoare.
Funcție virtuală
Pentru că funcția suprascrisă ar trebui legată dinamic de corpul funcției, facem funcția clasă de bază virtuală utilizând cuvântul cheie „virtual”. Această funcție virtuală este o funcție care este suprascrisă în clasa derivată, iar compilatorul efectuează o legare tardivă sau dinamică pentru această funcție.
Acum, să modificăm programul de mai sus pentru a include cuvântul cheie virtual după cum urmează:
#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 }
Ieșire:
Clasa :: Derivat
Deci, în definiția de bază a clasei de mai sus, am făcut funcția show_val ca „virtuală”. Deoarece funcția clasei de bază devine virtuală, atunci când atribuim obiectul clasei derivate pointerului clasei de bază și apelăm funcția show_val, legarea are loc în timpul rulării.
Astfel, întrucât indicatorul clasei de bază conține obiectul clasei derivate, corpul funcției show_val din clasa derivată este obligat să funcționeze show_val și, prin urmare, ieșirea.
În C ++, funcția suprascrisă în clasa derivată poate fi, de asemenea, privată. Compilatorul verifică doar tipul obiectului la momentul compilării și leagă funcția în timpul rulării, prin urmare nu face nicio diferență chiar dacă funcția este publică sau privată.
Rețineți că, dacă o funcție este declarată virtuală în clasa de bază, atunci va fi virtuală în toate clasele derivate.
Dar, până acum, nu am discutat despre modul în care funcțiile virtuale joacă un rol în identificarea funcției corecte pentru a fi legate sau, cu alte cuvinte, cu cât se întâmplă de fapt legarea tardivă.
Funcția virtuală este legată cu exactitate de corpul funcției în timpul rulării, utilizând conceptul de tabel virtual (VTABLE) și un indicator ascuns numit _vptr.
Ambele concepte sunt implementări interne și nu pot fi utilizate direct de program.
Funcționarea tabelului virtual și _vptr
Mai întâi, să înțelegem ce este o masă virtuală (VTABLE).
Compilatorul la momentul compilării configurează câte un VTABLE fiecare pentru o clasă care are funcții virtuale, precum și clasele care sunt derivate din clase care au funcții virtuale.
O VTABLE conține intrări care sunt indicatori de funcții către funcțiile virtuale care pot fi apelate de obiectele clasei. Există o intrare de pointer pentru fiecare funcție virtuală.
În cazul funcțiilor virtuale pure, această intrare este NULL. (Acesta este motivul pentru care nu putem instanția clasa abstractă).
Următoarea entitate, _vptr care se numește indicatorul vtable este un indicator ascuns pe care compilatorul îl adaugă clasei de bază. Acest _vptr indică tabelul clasei. Toate clasele derivate din această clasă de bază moștenesc _vptr.
Fiecare obiect al unei clase care conține funcțiile virtuale stochează intern acest _vptr și este transparent pentru utilizator. Fiecare apel către funcția virtuală folosind un obiect este apoi rezolvat folosind acest _vptr.
Să luăm un exemplu pentru a demonstra funcționarea vtable și _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); }
Ieșire:
Derived1_virtual :: function1_virtual ()
Base :: function2_virtual ()
În programul de mai sus, avem o clasă de bază cu două funcții virtuale și un destructor virtual. De asemenea, am derivat o clasă din clasa de bază și în aceasta; am suprascris o singură funcție virtuală. În funcția principală, indicatorul de clasă derivat este atribuit indicatorului de bază.
Apoi apelăm ambele funcții virtuale folosind un pointer de clasă de bază. Vedem că funcția suprascrisă este apelată atunci când este apelată și nu funcția de bază. În timp ce în al doilea caz, deoarece funcția nu este suprascrisă, funcția de clasă de bază se numește.
Acum să vedem cum este reprezentat intern programul de mai sus folosind vtable și _vptr.
Conform explicației anterioare, deoarece există două clase cu funcții virtuale, vom avea două tabele vtables - una pentru fiecare clasă. De asemenea, _vptr va fi prezent pentru clasa de bază.
Mai sus este prezentată reprezentarea picturală a modului în care va fi aspectul vtable pentru programul de mai sus. Tabela pentru clasa de bază este simplă. În cazul clasei derivate, numai funcția1_virtual este suprascrisă.
Prin urmare, vedem că în clasa derivată vtable, indicatorul funcției pentru function1_virtual indică funcția suprascrisă din clasa derivată. Pe de altă parte, indicatorul funcției pentru function2_virtual indică o funcție din clasa de bază.
Astfel, în programul de mai sus, când indicatorului de bază i se atribuie un obiect de clasă derivat, indicatorul de bază indică _vptr din clasa derivată.
Deci, atunci când se face apelul b-> function1_virtual (), funcția1_virtual din clasa derivată este apelată și când se face apelul funcției b-> function2_virtual (), deoarece acest indicator al funcției indică funcția clasei de bază, funcția clasei de bază se numește.
Funcții virtuale pure și clasa abstractă
Am văzut detalii despre funcțiile virtuale în C ++ în secțiunea noastră anterioară. În C ++, putem defini și un „ funcție virtuală pură ”Care este de obicei echivalat cu zero.
Funcția virtuală pură este declarată așa cum se arată mai jos.
virtual return_type function_name(arg list) = 0;
Clasa care are cel puțin o funcție virtuală pură numită „ clasa abstractă ”. Nu putem instanția niciodată clasa abstractă, adică nu putem crea un obiect al clasei abstracte.
Acest lucru se datorează faptului că știm că se face o intrare pentru fiecare funcție virtuală din VTABLE (tabel virtual). Dar, în cazul unei funcții virtuale pure, această intrare nu are nicio adresă, făcând-o incompletă. Deci, compilatorul nu permite crearea unui obiect pentru clasă cu intrare VTABLE incompletă.
Acesta este motivul pentru care nu putem instanția o clasă abstractă.
Exemplul de mai jos va demonstra funcția virtuală pură, precum și clasa 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(); }
Ieșire:
Anularea funcției virtuale pure în clasa derivată
În programul de mai sus, avem o clasă definită ca Base_abstract care conține o funcție virtuală pură care o face o clasă abstractă. Apoi derivăm o clasă „Derived_class” din Base_abstract și anulăm funcția virtuală pură printată în ea.
În funcția principală, nu este comentată prima linie. Acest lucru se datorează faptului că, dacă îl descomentăm, compilatorul va da o eroare deoarece nu putem crea un obiect pentru o clasă abstractă.
Dar a doua linie și codul funcționează. Putem crea cu succes un indicator de clasă de bază și apoi îi atribuim obiect de clasă derivat. Apoi, numim o funcție de imprimare care generează conținutul funcției de imprimare suprascris în clasa derivată.
Să enumerăm câteva caracteristici ale clasei abstracte pe scurt:
- Nu putem instanția o clasă abstractă.
- O clasă abstractă conține cel puțin o funcție virtuală pură.
- Deși nu putem instanția clasa abstractă, putem crea întotdeauna indicii sau referințe la această clasă.
- O clasă abstractă poate avea unele implementări, cum ar fi proprietăți și metode, împreună cu funcții virtuale pure.
- Când derivăm o clasă din clasa abstractă, clasa derivată ar trebui să anuleze toate funcțiile virtuale pure din clasa abstractă. Dacă nu a reușit acest lucru, atunci clasa derivată va fi, de asemenea, o clasă abstractă.
Destructori virtuali
Distructorii clasei pot fi declarați ca virtuali. Ori de câte ori facem upcast, adică atribuind obiectul clasei derivate unui pointer de clasă de bază, distructorii obișnuiți pot produce rezultate inacceptabile.
De exemplu,ia în considerare următoarea revoltare a distructorului obișnuit.
#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; }
Ieșire:
Clasa de bază :: Destructor
În programul de mai sus, avem o clasă derivată moștenită din clasa de bază. În principal, atribuim un obiect al clasei derivate unui pointer de clasă de bază.
În mod ideal, distructorul care se numește atunci când se numește „ștergeți b” ar fi trebuit să fie cel al clasei derivate, dar din rezultat putem vedea că distructorul clasei de bază este numit ca indicatorul clasei de bază indică acest lucru.
Din această cauză, destructorul clasei derivate nu este apelat și obiectul clasei derivate rămâne intact, rezultând astfel o scurgere de memorie. Soluția la acest lucru este de a face constructorul de clasă de bază virtual, astfel încât indicatorul obiectului să indice distructorul corect și să se efectueze distrugerea corectă a obiectelor.
Utilizarea destructorului virtual este prezentată în exemplul de mai jos.
#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; }
Ieșire:
Clasa derivată :: Destructor
Clasa de bază :: Destructor
Acesta este același program ca și programul anterior, cu excepția faptului că am adăugat un cuvânt cheie virtual în fața distructorului clasei de bază. Făcând virtual distructorul clasei de bază, am obținut rezultatul dorit.
Putem vedea că atunci când atribuim obiectul clasei derivate indicatorului clasei de bază și apoi ștergem indicatorul clasei de bază, distructorii sunt chemați în ordinea inversă a crearii obiectului. Aceasta înseamnă că mai întâi este chemat distructorul de clasă derivat și obiectul este distrus și apoi obiectul clasei de bază este distrus.
Notă: În C ++, constructorii nu pot fi niciodată virtuali, deoarece constructorii sunt implicați în construirea și inițializarea obiectelor. Prin urmare, avem nevoie ca toți constructorii să fie executați complet.
Concluzie
Polimorfismul de rulare este implementat folosind metoda suprascriere. Acest lucru funcționează bine atunci când apelăm metodele cu obiectele lor respective. Dar când avem un pointer de clasă de bază și apelăm metode suprascrise folosind pointerul de clasă de bază care indică obiectele clasei derivate, apar rezultate neașteptate din cauza legăturii statice.
Pentru a depăși acest lucru, folosim conceptul de funcții virtuale. Cu reprezentarea internă a vtables și _vptr, funcțiile virtuale ne ajută să apelăm cu acuratețe funcțiile dorite. În acest tutorial, am văzut în detaliu despre polimorfismul de rulare utilizat în C ++.
Cu aceasta, încheiem tutorialele noastre despre programarea orientată pe obiecte în C ++. Sperăm că acest tutorial va fi util pentru a obține o înțelegere mai bună și aprofundată a conceptelor de programare orientată obiect în C ++.
=> Vizitați aici pentru a afla C ++ de la zero.
Lectură recomandată
- Polimorfism în C ++
- Moștenirea în C ++
- Funcții prieten în C ++
- Clase și obiecte în C ++
- Utilizarea clasei Selenium Select pentru manipularea elementelor derulante pe o pagină web - Tutorial Selenium # 13
- Tutorial de funcții principale Python cu exemple practice
- Mașină virtuală Java: Cum ajută JVM la rularea aplicației Java
- Cum se configurează LoadRunner VuGen Script Files și Runtime Settings