runtime polymorphism c
En detaljerad studie av Runtime-polymorfism i C ++.
Runtime polymorfism är också känd som dynamisk polymorfism eller sen bindning. Vid körningspolymorfism löses funktionsanropet vid körningstid.
Däremot, för att kompilera tid eller statisk polymorfism, härleder kompilatorn objektet vid körtid och bestämmer sedan vilket funktionsanrop som ska bindas till objektet. I C ++ implementeras körtidspolymorfism med metodöverstyrning.
I denna handledning kommer vi att utforska allt om runympolymorfism i detalj.
=> Kontrollera ALLA C ++ -handledning här.
Vad du kommer att lära dig:
- Funktion Överstyrning
- Virtuell funktion
- Arbeta av virtuellt bord och _vptr
- Rena virtuella funktioner och abstrakt klass
- Virtuella förstörare
- Slutsats
- Rekommenderad läsning
Funktion Överstyrning
Funktionsstyrning är den mekanism som använder en funktion som definieras i basklassen återigen definieras i den härledda klassen. I det här fallet säger vi att funktionen åsidosätts i den härledda klassen.
Vi bör komma ihåg att funktionen åsidosättande inte kan göras inom en klass. Funktionen åsidosätts endast i den härledda klassen. Därför bör arv vara närvarande för funktionsstyrning.
Den andra saken är att funktionen från en basklass som vi åsidosätter ska ha samma signatur eller prototyp, dvs. den ska ha samma namn, samma returtyp och samma argumentlista.
Låt oss se ett exempel som visar metodöverstyrning.
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'< Produktion:
Klass :: Bas
Klass :: härledd
I ovanstående program har vi en basklass och en härledd klass. I basklassen har vi en funktion show_val som åsidosätts i den härledda klassen. I huvudfunktionen skapar vi ett objekt vardera av klassen Base och Derived och kallar show_val-funktionen med varje objekt. Det ger önskad effekt.
Ovanstående bindning av funktioner som använder objekt i varje klass är ett exempel på statisk bindning.
Låt oss nu se vad som händer när vi använder basklasspekaren och tilldelar härledda klassobjekt som dess innehåll.
Exempelprogrammet visas nedan:
#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:
Klass :: Bas
Nu ser vi att utgången är 'Klass :: Base'. Så oavsett vilket typobjekt baspekaren håller, matar programmet ut innehållet i funktionen för klassen vars baspekare är typen av. I detta fall utförs också statisk koppling.
För att göra baspekarens utdata, korrekta innehåll och korrekt länkning går vi för dynamisk bindning av funktioner. Detta uppnås med hjälp av virtuella funktionsmekanismer som förklaras i nästa avsnitt.
Virtuell funktion
För att den åsidosatta funktionen ska bindas dynamiskt till funktionens kropp, gör vi basklassfunktionen virtuell med hjälp av 'virtuellt' nyckelord. Denna virtuella funktion är en funktion som åsidosätts i den härledda klassen och kompilatorn utför sen eller dynamisk bindning för denna funktion.
Låt oss nu ändra programmet ovan för att inkludera det virtuella nyckelordet enligt följande:
#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:
Klass :: härledd
Så i klassdefinitionen ovan av Base gjorde vi show_val-funktionen som 'virtuell'. Eftersom basklassfunktionen görs virtuell, sker bindningen vid körning när vi tilldelar härledt klassobjekt till basklasspekaren och anropet show_val-funktion.
Eftersom basklasspekaren innehåller härledda klassobjekt är således show_val-funktionskroppen i den härledda klassen bunden till funktionen show_val och därmed utdata.
I C ++ kan den åsidosatta funktionen i härledd klass också vara privat. Kompilatorn kontrollerar bara objekttypen vid kompileringstidpunkten och binder funktionen vid körning, vilket gör att det inte gör någon skillnad även om funktionen är offentlig eller privat.
Observera att om en funktion förklaras virtuell i basklassen kommer den att vara virtuell i alla härledda klasser.
Men hittills har vi inte diskuterat hur exakt virtuella funktioner spelar en roll för att identifiera korrekt funktion som ska bindas eller med andra ord hur sen bindning faktiskt händer.
Den virtuella funktionen är bunden till funktionskroppen exakt vid körning med begreppet virtuellt bord (VTABLE) och en dold pekare som kallas _vptr.
Båda dessa begrepp är intern implementering och kan inte användas direkt av programmet.
Arbeta av virtuellt bord och _vptr
Låt oss först förstå vad en virtuell tabell (VTABLE) är.
Kompilatorn ställer in en VTABLE vardera för en klass med virtuella funktioner såväl som de klasser som härrör från klasser som har virtuella funktioner.
En VTABLE innehåller poster som är funktionspekare till de virtuella funktionerna som kan anropas av klassens objekt. Det finns en funktionspekare för varje virtuell funktion.
När det gäller rena virtuella funktioner är denna post NULL. (Detta är anledningen till att vi inte kan starta den abstrakta klassen).
Nästa enhet, _vptr som kallas vtable-pekaren, är en dold pekare som kompilatorn lägger till i basklassen. Denna _vptr pekar på klassens vtabell. Alla klasser härledda från denna basklass ärver _vptr.
Varje objekt i en klass som innehåller de virtuella funktionerna lagrar den här _vptr internt och är transparent för användaren. Varje samtal till virtuell funktion med ett objekt löses sedan med den här _vptr.
Låt oss ta ett exempel för att visa hur vtable och _vtr fungerar.
#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 ovanstående program har vi en basklass med två virtuella funktioner och en virtuell destruktor. Vi har också härledt en klass från basklassen och i det; vi har endast åsidosatt en virtuell funktion. I huvudfunktionen tilldelas den härledda klasspekaren baspekaren.
Sedan kallar vi båda de virtuella funktionerna med en basklasspekare. Vi ser att den åsidosatta funktionen kallas när den anropas och inte basfunktionen. Medan i det andra fallet, eftersom funktionen inte åsidosätts, kallas basklassfunktionen.
Låt oss nu se hur ovanstående program representeras internt med vtable och _vptr.
Enligt den tidigare förklaringen, eftersom det finns två klasser med virtuella funktioner, kommer vi att ha två vtabeller - en för varje klass. _Vptr kommer också att vara närvarande för basklassen.
Ovan visas den bildmässiga framställningen av hur vtabellayouten kommer att vara för ovanstående program. Vtabellen för basklassen är enkel. För den härledda klassen åsidosätts endast function1_virtual.
Därför ser vi att i den härledda klassen vtable pekar funktionspekaren för function1_virtual till den åsidosatta funktionen i den härledda klassen. Å andra sidan pekar funktionspekaren för function2_virtual till en funktion i basklassen.
Således i ovanstående program, när baspekaren tilldelas ett härledt klassobjekt, pekar baspekaren på _vptr för den härledda klassen.
Så när samtalet b-> function1_virtual () görs kallas funktionen1_virtual från den härledda klassen och när funktionen call b-> function2_virtual () görs, eftersom denna funktionspekare pekar på basklassfunktionen, basklassfunktionen kallas.
Rena virtuella funktioner och abstrakt klass
Vi har sett detaljer om virtuella funktioner i C ++ i vårt tidigare avsnitt. I C ++ kan vi också definiera en ” ren virtuell funktion ”Som vanligtvis är lika med noll.
Den rena virtuella funktionen deklareras enligt nedan.
virtual return_type function_name(arg list) = 0;
Klassen som har minst en ren virtuell funktion som kallas en ” abstrakt klass ”. Vi kan aldrig instansiera den abstrakta klassen, dvs. vi kan inte skapa ett objekt av den abstrakta klassen.
Detta beror på att vi vet att en post görs för varje virtuell funktion i VTABLE (virtuell tabell). Men i händelse av en ren virtuell funktion är denna post utan någon adress vilket gör den ofullständig. Så kompilatorn tillåter inte att skapa ett objekt för klassen med ofullständig VTABLE-post.
Detta är anledningen till att vi inte kan starta en abstrakt klass.
Nedanstående exempel visar ren virtuell funktion samt abstrakt klass.
#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:
Åsidosätt ren virtuell funktion i den härledda klassen
I ovanstående program har vi en klass definierad som Base_abstract som innehåller en ren virtuell funktion som gör den till en abstrakt klass. Sedan härleder vi en klass “Derived_class” från Base_abstract och åsidosätter den rena virtuella funktionsutskriften i den.
I huvudfunktionen kommenteras inte den första raden. Detta beror på att om vi avmarkerar det kommer kompilatorn att ge ett fel eftersom vi inte kan skapa ett objekt för en abstrakt klass.
Men den andra raden framåt fungerar koden. Vi kan framgångsrikt skapa en basklasspekare och sedan tilldela den härledda klassobjekt till den. Därefter kallar vi en utskriftsfunktion som matar ut innehållet i utskriftsfunktionen åsidosatt i den härledda klassen.
Låt oss kortfattat lista några egenskaper hos abstrakt klass:
- Vi kan inte starta en abstrakt klass.
- En abstrakt klass innehåller minst en ren virtuell funktion.
- Även om vi inte kan initiera abstrakt klass kan vi alltid skapa pekare eller referenser till den här klassen.
- En abstrakt klass kan ha en viss implementering som egenskaper och metoder tillsammans med rena virtuella funktioner.
- När vi härleder en klass från abstraktklassen, bör den härledda klassen åsidosätta alla rena virtuella funktioner i abstraktklassen. Om det misslyckades med att göra det kommer den härledda klassen också att vara en abstrakt klass.
Virtuella förstörare
Förstörare av klassen kan förklaras som virtuella. Närhelst vi gör upcast, dvs. tilldelar det härledda klassobjektet till en basklasspekare, kan de vanliga destruktörerna ge oacceptabla resultat.
Till exempel,överväga följande utstötning av den vanliga destruktören.
#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:
Basklass :: Destructor
I ovanstående program har vi en ärvt härledd klass från basklassen. I huvudsak tilldelar vi ett objekt av den härledda klassen till en basklasspekare.
skillnad mellan c ++ och c
Helst skulle destruktorn som kallas när 'delete b' kallas ha varit den för härledd klass men vi kan se från utgången att förstöraren av basklassen kallas som basklasspekare pekar på det.
På grund av detta anropas inte den härledda klassdestruktorn och det härledda klassobjektet förblir intakt vilket resulterar i en minnesläcka. Lösningen på detta är att göra basklasskonstruktörer virtuella så att objektpekaren pekar på att korrigera förstöraren och korrekt förstörelse av objekt utförs.
Användningen av virtuell destruktör visas i exemplet nedan.
#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:
Avledad klass :: Destructor
Basklass :: Destructor
Detta är samma program som föregående program förutom att vi har lagt till ett virtuellt nyckelord framför basklassdestruktören. Genom att göra basklassdestruktören virtuell har vi uppnått önskad effekt.
Vi kan se att när vi tilldelar härledt klassobjekt till basklasspekaren och sedan tar bort basklasspekaren, kallas destruktorer i omvänd ordning för skapande av objekt. Detta innebär att först den härledda klassdestruktören anropas och objektet förstörs och sedan förstörs basklassobjektet.
Notera: I C ++ kan konstruktörer aldrig vara virtuella, eftersom konstruktörer är inblandade i att konstruera och initialisera objekten. Därför behöver vi alla konstruktörer utföras helt.
Slutsats
Runtime polymorfism implementeras med metodöverstyrning. Detta fungerar bra när vi kallar metoderna med deras respektive objekt. Men när vi har en basklasspekare och vi kallar överstyrda metoder med basklasspekaren som pekar på de härledda klassobjekten, uppstår oväntade resultat på grund av statisk länkning.
För att övervinna detta använder vi begreppet virtuella funktioner. Med den interna representationen av vtables och _vptr hjälper virtuella funktioner oss att exakt anropa önskade funktioner. I denna handledning har vi sett i detalj om runtime polymorfism som används i C ++.
Med detta avslutar vi våra handledning om objektorienterad programmering i C ++. Vi hoppas att den här guiden kommer att vara till hjälp för att få en bättre och grundligare förståelse av objektorienterade programmeringskoncept i C ++.
=> Besök här för att lära dig C ++ från Scratch.
Rekommenderad läsning
- Polymorfism i C ++
- Arv i C ++
- Vänfunktioner i C ++
- Klasser och objekt i C ++
- Användning av Selen Select Class för hantering av rullgardinselement på en webbsida - Selen Tutorial # 13
- Pythons huvudfunktionshandledning med praktiska exempel
- Java Virtual Machine: Hur JVM hjälper till att köra Java-applikationen
- Så här ställer du in LoadRunner VuGen-skriptfiler och Runtime-inställningar