runtime polymorphism c
En detaljeret undersøgelse af løbetidspolymorfisme i C ++.
Runtime polymorfisme er også kendt som dynamisk polymorfisme eller sen binding. I runtime polymorfisme løses funktionsopkaldet ved kørselstid.
I modsætning hertil, for at kompilere tid eller statisk polymorfisme, udleder compileren objektet ved kørselstid og beslutter derefter, hvilket funktionskald der skal bindes til objektet. I C ++ er runtime polymorfisme implementeret ved hjælp af metode tilsidesættelse.
I denne vejledning vil vi udforske alt om runtime polymorfisme i detaljer.
=> Tjek ALLE C ++ tutorials her.
Hvad du lærer:
- Overstyring af funktion
- Virtuel funktion
- Arbejde af virtuelt bord og _vptr
- Ren virtuelle funktioner og abstrakt klasse
- Virtuelle destruktører
- Konklusion
- Anbefalet læsning
Overstyring af funktion
Funktionsoverstyring er den mekanisme, hvor en funktion defineret i basisklassen igen defineres i den afledte klasse. I dette tilfælde siger vi, at funktionen er tilsidesat i den afledte klasse.
Vi skal huske, at funktionen tilsidesættelse ikke kan udføres inden for en klasse. Funktionen tilsidesættes kun i den afledte klasse. Derfor skal arv være til stede for tilsidesættelse af funktion.
Den anden ting er, at funktionen fra en baseklasse, som vi tilsidesætter, skal have den samme signatur eller prototype, dvs. den skal have samme navn, samme returtype og samme argumentliste.
Lad os se et eksempel, der demonstrerer metode tilsidesættelse.
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'< Produktion:
Klasse :: Base
Klasse :: Afledt
I ovenstående program har vi en basisklasse og en afledt klasse. I baseklassen har vi en funktion show_val, der tilsidesættes i den afledte klasse. I hovedfunktionen opretter vi et objekt hver af Base og Derived klasse og kalder show_val-funktionen med hvert objekt. Det producerer det ønskede output.
Ovenstående binding af funktioner ved hjælp af objekter i hver klasse er et eksempel på statisk binding.
Lad os nu se, hvad der sker, når vi bruger baseklassemarkøren og tildeler afledte klasseobjekter som dens indhold.
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 }
Produktion:
Klasse :: Base
Nu ser vi, at output er 'Class :: Base'. Så uanset hvilket typeobjekt basemarkøren holder, udsender programmet indholdet af klassens funktion, hvis basismarkør er typen. I dette tilfælde udføres også statisk sammenkædning.
For at få basispointeroutput, korrekt indhold og korrekt sammenkædning går vi efter dynamisk binding af funktioner. Dette opnås ved hjælp af virtuelle funktionsmekanismer, som forklares i det næste afsnit.
Virtuel funktion
For den tilsidesatte funktion skal være bundet dynamisk til funktionselementet, gør vi baseklassefunktionen virtuel ved hjælp af det 'virtuelle' nøgleord. Denne virtuelle funktion er en funktion, der tilsidesættes i den afledte klasse, og compileren udfører sen eller dynamisk binding til denne funktion.
Lad os nu ændre ovenstående program for at inkludere det virtuelle nøgleord 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 }
Produktion:
Klasse :: Afledt
Så i ovenstående klassedefinition af Base fik vi show_val til at fungere som 'virtuel'. Da baseklassefunktionen gøres virtuel, når vi tildeler afledt klasseobjekt til baseklassemarkøren og kalder show_val-funktion, sker bindingen ved kørsel.
Da baseklassemarkøren indeholder afledt klasseobjekt, er show_val-funktionslegemet i den afledte klasse således bundet til funktionen show_val og dermed output.
I C ++ kan den tilsidesatte funktion i afledt klasse også være privat. Compileren kontrollerer kun objekttypen på kompileringstidspunktet og binder funktionen på kørselstid, hvorfor det ikke betyder nogen forskel, selvom funktionen er offentlig eller privat.
Bemærk, at hvis en funktion erklæres virtuel i basisklassen, vil den være virtuel i alle de afledte klasser.
Men indtil nu har vi ikke diskuteret, hvordan nøjagtige virtuelle funktioner spiller en rolle i at identificere den korrekte funktion, der skal bindes, eller med andre ord, hvordan sen binding faktisk sker.
Den virtuelle funktion er bundet til funktionskroppen nøjagtigt ved kørsel ved hjælp af begrebet virtuelt bord (VTABLE) og en skjult markør kaldes _vptr.
Begge disse begreber er intern implementering og kan ikke bruges direkte af programmet.
Arbejde af virtuelt bord og _vptr
Lad os først forstå, hvad en virtuel tabel (VTABLE) er.
Compileren opretter ved kompileringstid en VTABLE hver for en klasse med virtuelle funktioner såvel som de klasser, der stammer fra klasser, der har virtuelle funktioner.
En VTABLE indeholder poster, der er funktionspegere til de virtuelle funktioner, der kan kaldes af klassens objekter. Der er en funktionsmarkørindgang for hver virtuel funktion.
I tilfælde af rene virtuelle funktioner er denne post NULL. (Dette er grunden til, at vi ikke kan instantiere den abstrakte klasse).
Den næste enhed, _vptr, der kaldes vtable-markøren, er en skjult markør, som compileren tilføjer til basisklassen. Denne _vptr peger på klassens vtabel. Alle klasser afledt af denne basisklasse arver _vptr.
Hvert objekt i en klasse, der indeholder de virtuelle funktioner, gemmer denne _vptr internt og er gennemsigtig for brugeren. Hvert opkald til virtuel funktion ved hjælp af et objekt løses derefter ved hjælp af denne _vptr.
Lad os tage et eksempel for at demonstrere funktionen af vtable og _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); }
Produktion:
Derived1_virtual :: function1_virtual ()
Base :: function2_virtual ()
I ovenstående program har vi en baseklasse med to virtuelle funktioner og en virtuel destruktor. Vi har også afledt en klasse fra basisklassen og i den; vi har kun tilsidesat en virtuel funktion. I hovedfunktionen tildeles den afledte klassemarkør til basismarkøren.
Derefter kalder vi begge de virtuelle funktioner ved hjælp af en basisklassemarkør. Vi ser, at den tilsidesatte funktion kaldes, når den kaldes, og ikke basisfunktionen. Mens i det andet tilfælde kaldes basisklassefunktionen, da funktionen ikke tilsidesættes.
Lad os nu se, hvordan ovenstående program er repræsenteret internt ved hjælp af vtable og _vptr.
I henhold til den tidligere forklaring, da der er to klasser med virtuelle funktioner, vil vi have to vtabeller - en for hver klasse. _Vptr vil også være til stede for basisklassen.

Ovenfor er vist den billedlige gengivelse af, hvordan vtabellayoutet vil være for ovenstående program. Vtabellen for basisklassen er ligetil. I tilfælde af den afledte klasse tilsidesættes kun function1_virtual.
Derfor ser vi, at funktionsmarkøren for function1_virtual i den afledte klasse vtable peger på den tilsidesatte funktion i den afledte klasse. På den anden side peger funktionsmarkøren for function2_virtual til en funktion i basisklassen.
Således i det ovennævnte program, når basismarkøren tildeles et afledt klasseobjekt, peger basismarkøren på _vptr for den afledte klasse.
Så når opkaldet b-> funktion1_virtual () foretages, kaldes funktionen1_virtual fra den afledte klasse, og når funktionskaldet b-> function2_virtual () foretages, da denne funktionsmarkør peger på basisklassefunktionen, baseklassefunktionen Hedder.
Ren virtuelle funktioner og abstrakt klasse
Vi har set detaljer om virtuelle funktioner i C ++ i vores forrige afsnit. I C ++ kan vi også definere en “ ren virtuel funktion ”Der er normalt lig med nul.
Den rene virtuelle funktion erklæres som vist nedenfor.
virtual return_type function_name(arg list) = 0;
Klassen, der har mindst en ren virtuel funktion, der kaldes en ” abstrakt klasse ”. Vi kan aldrig instantiere den abstrakte klasse, dvs. vi kan ikke skabe et objekt for den abstrakte klasse.
Dette skyldes, at vi ved, at der foretages en post for hver virtuel funktion i VTABLE (virtuel tabel). Men i tilfælde af en ren virtuel funktion er denne post uden nogen adresse, hvilket gør den ufuldstændig. Så compileren tillader ikke oprettelse af et objekt til klassen med ufuldstændig VTABLE-post.
Dette er grunden til, at vi ikke kan instantiere en abstrakt klasse.
Nedenstående eksempel viser ren virtuel funktion såvel som 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(); }
Produktion:
Tilsidesættelse af ren virtuel funktion i den afledte klasse
I ovenstående program har vi en klasse defineret som Base_abstract, som indeholder en ren virtuel funktion, der gør det til en abstrakt klasse. Derefter udleder vi en klasse 'Derived_class' fra Base_abstract og tilsidesætter den rene virtuelle funktionsprint i den.
I hovedfunktionen kommenteres ikke den første linje. Dette skyldes, at hvis vi ikke kommenterer det, vil compileren give en fejl, da vi ikke kan oprette et objekt til en abstrakt klasse.
Men den anden linje og videre fungerer koden. Vi kan med succes oprette en basisklassemarkør, og derefter tildele vi afledt klasseobjekt til den. Dernæst kalder vi en udskrivningsfunktion, der sender indholdet af udskrivningsfunktionen tilsidesat i den afledte klasse.
Lad os kort nævne nogle karakteristika ved abstrakt klasse:
- Vi kan ikke instantiere en abstrakt klasse.
- En abstrakt klasse indeholder mindst en ren virtuel funktion.
- Selvom vi ikke kan instantere abstrakt klasse, kan vi altid oprette henvisninger eller henvisninger til denne klasse.
- En abstrakt klasse kan have nogle implementeringer som egenskaber og metoder sammen med rene virtuelle funktioner.
- Når vi udleder en klasse fra den abstrakte klasse, skal den afledte klasse tilsidesætte alle de rene virtuelle funktioner i den abstrakte klasse. Hvis det ikke lykkedes, vil den afledte klasse også være en abstrakt klasse.
Virtuelle destruktører
Destruktører i klassen kan erklæres som virtuelle. Når vi gør upcast, dvs. tildele det afledte klasseobjekt til en basisklassemarkør, kan de almindelige destruktører producere uacceptable resultater.
For eksempel,overvej følgende udsendelse af den almindelige destruktør.
#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; }
Produktion:
hvordan man åbner en .swf
Baseklasse :: Destructor
I ovenstående program har vi en nedarvet afledt klasse fra basisklassen. I det væsentlige tildeler vi et objekt fra den afledte klasse til en basisklassemarkør.
Ideelt set burde destruktoren, der kaldes, når 'slet b' kaldes, have været den afledte klasse, men vi kan fra output se, at destruktoren fra baseklassen kaldes som baseklassemarkør peger på det.
På grund af dette kaldes den afledte klassedestruktør ikke, og det afledte klasseobjekt forbliver intakt, hvilket resulterer i en hukommelseslækage. Løsningen på dette er at gøre baseklassekonstruktør virtuel, så objektmarkøren peger på at korrigere destruktor og korrekt destruktion af objekter udføres.
Brugen af virtuel destruktor er vist i nedenstående eksempel.
#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; }
Produktion:
Afledt klasse :: Destructor
Baseklasse :: Destructor
Dette er det samme program som det foregående program, bortset fra at vi har tilføjet et virtuelt nøgleord foran basisklassedestruktøren. Ved at gøre baseklasse destruktør virtuel har vi opnået den ønskede output.
Vi kan se, at når vi tildeler afledt klasseobjekt til baseklassemarkøren og derefter sletter basisklassemarkøren, kaldes destruktører i omvendt rækkefølge af objektoprettelse. Dette betyder, at først kaldes den afledte klassedestruktør, og objektet ødelægges, og derefter ødelægges basisklasseobjektet.
Bemærk: I C ++ kan konstruktører aldrig være virtuelle, da konstruktører er involveret i at konstruere og initialisere objekterne. Derfor har vi brug for, at alle konstruktører skal udføres fuldstændigt.
Konklusion
Runtime polymorfisme implementeres ved hjælp af metode tilsidesættelse. Dette fungerer fint, når vi kalder metoderne med deres respektive objekter. Men når vi har en basisklassemarkør, og vi kalder tilsidesatte metoder ved hjælp af baseklassemarkøren, der peger på de afledte klasseobjekter, opstår der uventede resultater på grund af statisk sammenkædning.
For at overvinde dette bruger vi begrebet virtuelle funktioner. Med den interne repræsentation af vtables og _vptr hjælper virtuelle funktioner os med nøjagtighed at kalde de ønskede funktioner. I denne vejledning har vi set detaljeret om runtime polymorfisme brugt i C ++.
Med dette afslutter vi vores tutorials om objektorienteret programmering i C ++. Vi håber, at denne tutorial vil være nyttigt for at få en bedre og grundig forståelse af objektorienterede programmeringskoncepter i C ++.
=> Besøg her for at lære C ++ fra bunden.
Anbefalet læsning
- Polymorfisme i C ++
- Arv i C ++
- Venfunktioner i C ++
- Klasser og objekter i C ++
- Brug af Selen Select Class til håndtering af dropdown-elementer på en webside - Selen Tutorial # 13
- Pythons hovedfunktionsvejledning med praktiske eksempler
- Java Virtual Machine: Hvordan JVM hjælper med at køre Java-applikationer
- Sådan opsættes LoadRunner VuGen Script-filer og Runtime-indstillinger