runtime polymorphism c
En detaljert studie av løpetidspolymorfisme i C ++.
Runtime polymorfisme er også kjent som dynamisk polymorfisme eller sen binding. I løpetidspolymorfisme løses funksjonsanropet på kjøretid.
I motsetning til dette, for å kompilere tid eller statisk polymorfisme, trekker kompilatoren ut objektet på kjøretid og bestemmer deretter hvilket funksjonsanrop som skal bindes til objektet. I C ++ implementeres polymorfisme med kjøretid ved hjelp av metodeoverstyring.
I denne veiledningen vil vi utforske alt om runtime polymorfisme i detalj.
=> Sjekk ALLE C ++ opplæringsprogrammer her.
Hva du vil lære:
- Funksjon Overstyring
- Virtuell funksjon
- Arbeid av virtuelt bord og _vptr
- Rene virtuelle funksjoner og abstrakt klasse
- Virtuelle destruktører
- Konklusjon
- Anbefalt lesing
Funksjon Overstyring
Funksjonsoverstyring er den mekanismen som en funksjon definert i basisklassen igjen defineres i den avledede klassen. I dette tilfellet sier vi at funksjonen overstyres i den avledede klassen.
Vi bør huske at funksjonsoverstyring ikke kan gjøres i en klasse. Funksjonen overstyres bare i den avledede klassen. Derfor bør arv være til stede for funksjonsoverstyring.
Den andre tingen er at funksjonen fra en basisklasse som vi overstyrer, skal ha samme signatur eller prototype, dvs. den skal ha samme navn, samme returtype og samme argumentliste.
La oss se et eksempel som demonstrerer metodeoverstyring.
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'< Produksjon:
Klasse :: Base
Klasse :: Avledet
I programmet ovenfor har vi en basisklasse og en avledet klasse. I basisklassen har vi en funksjon show_val som overstyres i den avledede klassen. I hovedfunksjonen lager vi et objekt hver av Base og Derived-klassen og kaller show_val-funksjonen med hvert objekt. Det gir ønsket utgang.
Ovennevnte binding av funksjoner ved bruk av objekter i hver klasse er et eksempel på statisk binding.
La oss nå se hva som skjer når vi bruker baseklassepekeren og tilordner avledede klasseobjekter som innholdet.
Eksempelprogrammet er vist nedenfor:
#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 }
Produksjon:
Klasse :: Base
Nå ser vi at utgangen er 'Class :: Base'. Så uavhengig av hvilket typeobjekt basepekeren holder, sender programmet ut innholdet i funksjonen til klassen hvis basepeker er typen. I dette tilfellet utføres også statisk kobling.
For å lage basepekeren, korrekt innhold og riktig kobling, går vi for dynamisk binding av funksjoner. Dette oppnås ved hjelp av virtuelle funksjonsmekanismer som forklares i neste avsnitt.
Virtuell funksjon
For den overstyrte funksjonen skal være bundet dynamisk til funksjonselementet, gjør vi baseklassefunksjonen virtuell ved hjelp av det 'virtuelle' nøkkelordet. Denne virtuelle funksjonen er en funksjon som overstyres i den avledede klassen, og kompilatoren utfører sen eller dynamisk binding for denne funksjonen.
La oss nå endre programmet ovenfor for å inkludere det virtuelle nøkkelordet som følger:
#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 }
Produksjon:
Klasse :: Avledet
Så i klassedefinisjonen ovenfor av Base, fikk vi show_val til å fungere som 'virtuell'. Når baseklassefunksjonen blir gjort virtuell, skjer bindingen ved kjøretid når vi tildeler avledet klasseobjekt til baseklassepekeren og kaller show_val-funksjonen.
Således som grunnklasspekeren inneholder avledet klasseobjekt, er funksjonslegemet show_val i den avledede klassen bundet til funksjonen show_val og derav utgangen.
I C ++ kan den overstyrte funksjonen i avledet klasse også være privat. Kompilatoren sjekker bare typen objekt på kompileringstidspunktet og binder funksjonen på kjøretid, og det gjør derfor ingen forskjell selv om funksjonen er offentlig eller privat.
Merk at hvis en funksjon er erklært virtuell i basisklassen, vil den være virtuell i alle de avledede klassene.
Men til nå har vi ikke diskutert hvordan nøyaktig virtuelle funksjoner spiller en rolle i å identifisere riktig funksjon som skal bindes, eller med andre ord, hvor sen binding faktisk skjer.
Den virtuelle funksjonen er bundet til funksjonsdelen nøyaktig ved kjøretid ved å bruke konseptet virtuelt bord (VTABLE) og en skjult peker kalt _vptr.
Begge disse konseptene er intern implementering og kan ikke brukes direkte av programmet.
Arbeid av virtuelt bord og _vptr
La oss først forstå hva et virtuelt bord (VTABLE) er.
Kompilatoren ved kompileringstid setter opp en VTABLE hver for en klasse som har virtuelle funksjoner, så vel som klassene som er avledet fra klasser som har virtuelle funksjoner.
En VTABLE inneholder oppføringer som er funksjonspekere til de virtuelle funksjonene som kan kalles av klassens objekter. Det er en funksjonspekeroppføring for hver virtuelle funksjon.
Når det gjelder rene virtuelle funksjoner, er denne oppføringen NULL. (Dette er grunnen til at vi ikke kan instantiere den abstrakte klassen).
Neste enhet, _vptr som kalles vtable-pekeren, er en skjult peker som kompilatoren legger til i basisklassen. Denne _vptr peker på klassens vtabell. Alle klassene avledet fra denne basisklassen arver _vptr.
Hvert objekt i en klasse som inneholder de virtuelle funksjonene internt lagrer denne _vptr og er gjennomsiktig for brukeren. Hver samtale til virtuell funksjon ved hjelp av et objekt løses deretter ved hjelp av denne _vptr.
La oss ta et eksempel for å demonstrere hvordan vtable og _vtr fungerer.
#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); }
Produksjon:
Derived1_virtual :: function1_virtual ()
Base :: function2_virtual ()
I programmet ovenfor har vi en baseklasse med to virtuelle funksjoner og en virtuell destruktor. Vi har også avledet en klasse fra basisklassen og i det; vi har kun overstyrt en virtuell funksjon. I hovedfunksjonen tildeles den avledede klassepekeren til basispekeren.
Deretter kaller vi begge de virtuelle funksjonene ved hjelp av en basisklasspeker. Vi ser at den overstyrte funksjonen kalles når den kalles og ikke basisfunksjonen. Mens i det andre tilfellet, da funksjonen ikke overstyres, kalles baseklassefunksjonen.
La oss nå se hvordan programmet ovenfor er representert internt ved hjelp av vtable og _vptr.
I følge den tidligere forklaringen, da det er to klasser med virtuelle funksjoner, vil vi ha to vtabeller - en for hver klasse. _Vptr vil også være til stede for basisklassen.

Ovenfor vises den billedlige fremstillingen av hvordan tabelloppsettet vil være for programmet ovenfor. Tabellen for basisklassen er grei. For den avledede klassen overstyres bare function1_virtual.
Derfor ser vi at i den avledede klassen vtable peker funksjonspekeren for function1_virtual til den overstyrte funksjonen i den avledede klassen. På den annen side peker funksjonspekeren for function2_virtual til en funksjon i basisklassen.
Således i det ovennevnte programmet, når basispekeren er tildelt et avledet klasseobjekt, peker basispekeren på _vptr for den avledede klassen.
Så når samtalen b-> function1_virtual () blir utført, kalles function1_virtual fra den avledede klassen, og når funksjonen call b-> function2_virtual () blir gjort, da denne funksjonspekeren peker på baseklassefunksjonen, baseklassefunksjonen er kalt.
Rene virtuelle funksjoner og abstrakt klasse
Vi har sett detaljer om virtuelle funksjoner i C ++ i vår forrige seksjon. I C ++ kan vi også definere en ren virtuell funksjon ”Som vanligvis er lik null.
Den rene virtuelle funksjonen er erklært som vist nedenfor.
virtual return_type function_name(arg list) = 0;
Klassen som har minst en ren virtuell funksjon som kalles en “ abstrakt klasse ”. Vi kan aldri instansiere den abstrakte klassen, dvs. vi kan ikke skape et objekt for den abstrakte klassen.
Dette er fordi vi vet at det blir gjort en oppføring for hver virtuelle funksjon i VTABLE (virtuell tabell). Men i tilfelle en ren virtuell funksjon, er denne oppføringen uten adresse, og dermed blir den ufullstendig. Så kompilatoren tillater ikke å lage et objekt for klassen med ufullstendig VTABLE-oppføring.
Dette er grunnen til at vi ikke kan starte en abstrakt klasse.
Eksemplet nedenfor vil demonstrere ren virtuell funksjon samt abstrakt klasse.
#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(); }
Produksjon:
Overstyring av ren virtuell funksjon i den avledede klassen
I programmet ovenfor har vi en klasse definert som Base_abstract som inneholder en ren virtuell funksjon som gjør den til en abstrakt klasse. Deretter utleder vi klassen “Derived_class” fra Base_abstract og overstyrer den rene virtuelle funksjonsutskriften i den.
I hovedfunksjonen er ikke den første linjen kommentert. Dette er fordi hvis vi ikke kommenterer det, vil kompilatoren gi en feil da vi ikke kan lage et objekt for en abstrakt klasse.
Men den andre linjen og fremover fungerer koden. Vi kan opprette en baseklassepeker, og deretter tildele den avledet klasseobjekt til den. Deretter kaller vi en utskriftsfunksjon som sender innholdet i utskriftsfunksjonen overstyrt i den avledede klassen.
hvordan åpne en eps-fil i Windows 10
La oss kort oppsummere noen kjennetegn ved abstrakt klasse:
- Vi kan ikke starte en abstrakt klasse.
- En abstrakt klasse inneholder minst en ren virtuell funksjon.
- Selv om vi ikke kan instantiere abstrakt klasse, kan vi alltid lage pekere eller referanser til denne klassen.
- En abstrakt klasse kan ha noen implementeringer som egenskaper og metoder sammen med rene virtuelle funksjoner.
- Når vi henter en klasse fra abstraktklassen, bør den avledede klassen overstyre alle de rene virtuelle funksjonene i abstraktklassen. Hvis den ikke klarte å gjøre det, vil den avledede klassen også være en abstrakt klasse.
Virtuelle destruktører
Destruktører i klassen kan erklæres som virtuelle. Når vi gjør upcast, dvs. tildele det avledede klasseobjektet til en baseklassepeker, kan de vanlige destruktørene gi uakseptable resultater.
For eksempel,vurdere følgende oppkast fra den vanlige ødeleggeren.
#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; }
Produksjon:
Grunnklasse :: Destructor
I programmet ovenfor har vi en arvet avledet klasse fra basisklassen. I hovedsak tilordner vi et objekt av den avledede klassen til en basisklassepeker.
Ideelt sett burde destruktoren som kalles når 'slett b' kalles ha vært den fra avledet klasse, men vi kan se fra utdataene at destruktoren til baseklassen kalles som baseklasspekeren peker på det.
På grunn av dette kalles ikke den avledede klassedestruktøren, og det avledede klasseobjektet forblir intakt og resulterer i en minnelekkasje. Løsningen på dette er å gjøre baseklasskonstruktøren virtuell slik at objektpekeren peker mot riktig destruktor og riktig ødeleggelse av objekter blir utført.
Bruken av virtuell destruktør er vist i eksemplet nedenfor.
#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; }
Produksjon:
Avledet klasse :: Destructor
Grunnklasse :: Destructor
Dette er det samme programmet som forrige program, bortsett fra at vi har lagt til et virtuelt nøkkelord foran baseklassedestruktøren. Ved å gjøre baseklasse destruktør virtuell, har vi oppnådd ønsket produksjon.
Vi kan se at når vi tilordner avledet klasseobjekt til baseklassepekeren og deretter sletter baseklassepekeren, kalles destruktører i omvendt rekkefølge av objektoppretting. Dette betyr at først den avledede klassedestruktøren kalles og objektet blir ødelagt og deretter baseklasseobjektet blir ødelagt.
Merk: I C ++ kan konstruktører aldri være virtuelle, da konstruktører er involvert i å konstruere og initialisere objektene. Derfor trenger vi at alle konstruktørene blir utført helt.
Konklusjon
Runtime polymorfisme er implementert ved hjelp av metodeoverstyring. Dette fungerer bra når vi kaller metodene med deres respektive objekter. Men når vi har en baseklassepeker og vi kaller overstyrte metoder ved hjelp av baseklassepekeren som peker på de avledede klasseobjektene, oppstår uventede resultater på grunn av statisk kobling.
For å overvinne dette bruker vi konseptet virtuelle funksjoner. Med den interne representasjonen av vtables og _vptr, hjelper virtuelle funksjoner oss med å kalle de ønskede funksjonene nøyaktig. I denne veiledningen har vi sett i detalj om kjøretids polymorfisme brukt i C ++.
Med dette avslutter vi opplæringene våre om objektorientert programmering i C ++. Vi håper denne opplæringen vil være nyttig for å få en bedre og grundigere forståelse av objektorienterte programmeringskonsepter i C ++.
=> Besøk her for å lære C ++ fra grunnen.
Anbefalt lesing
- Polymorfisme i C ++
- Arv i C ++
- Vennfunksjoner i C ++
- Klasser og objekter i C ++
- Bruk av selen valgt klasse for håndtering av nedtrekkselementer på en webside - Selenium-veiledning nr. 13
- Python hovedveiledning med praktiske eksempler
- Java Virtual Machine: Hvordan JVM hjelper med å kjøre Java-applikasjoner
- Slik konfigurerer du LoadRunner VuGen-skriptfiler og kjøretidsinnstillinger