runtime polymorphism c
Een gedetailleerde studie van runtime-polymorfisme in C ++.
Runtime-polymorfisme is ook bekend als dynamisch polymorfisme of late binding. In runtime-polymorfisme wordt de functieaanroep tijdens runtime opgelost.
Om daarentegen tijd of statisch polymorfisme te compileren, leidt de compiler het object af tijdens runtime en beslist vervolgens welke functieaanroep aan het object moet worden gebonden. In C ++ wordt runtime-polymorfisme geïmplementeerd met behulp van methode-overschrijving.
In deze tutorial zullen we alles over runtime polymorfisme in detail onderzoeken.
Bekijk hier ALLE C ++ Tutorials.
Wat je leert:
- Functie overschrijven
- Virtuele functie
- Werken met virtuele tafel en _vptr
- Pure virtuele functies en abstracte klasse
- Virtuele vernietigers
- Gevolgtrekking
- Aanbevolen literatuur
Functie overschrijven
Functieoverschrijving is het mechanisme waarmee een functie die in de basisklasse is gedefinieerd, opnieuw wordt gedefinieerd in de afgeleide klasse. In dit geval zeggen we dat de functie wordt overschreven in de afgeleide klasse.
We moeten niet vergeten dat het overschrijven van functies niet binnen een klas kan worden gedaan. De functie wordt alleen overschreven in de afgeleide klasse. Daarom moet overerving aanwezig zijn om de functie te overschrijven.
Het tweede ding is dat de functie van een basisklasse die we overschrijven dezelfde handtekening of hetzelfde prototype moet hebben, d.w.z. dat het dezelfde naam, hetzelfde retourneringstype en dezelfde argumentenlijst moet hebben.
Laten we een voorbeeld bekijken dat het overschrijven van methoden laat zien.
Uitgang:
Klasse :: Basis
Klasse :: Afgeleid
In het bovenstaande programma hebben we een basisklasse en een afgeleide klasse. In de basisklasse hebben we een functie show_val die wordt overschreven in de afgeleide klasse. In de hoofdfunctie maken we een object van elk van de Base- en Derived-klassen en roepen we de functie show_val aan met elk object. Het produceert de gewenste output.
De bovenstaande binding van functies met behulp van objecten van elke klasse is een voorbeeld van statische binding.
Laten we nu eens kijken wat er gebeurt als we de basisklasse-pointer gebruiken en afgeleide klasseobjecten als inhoud toewijzen.
Het voorbeeldprogramma wordt hieronder getoond:
Uitgang:
Klasse :: Basis
Nu zien we dat de uitvoer 'Class :: Base' is. Dus ongeacht welk type object de basispointer vasthoudt, geeft het programma de inhoud weer van de functie van de klasse waarvan de basispointer het type is van. In dit geval wordt ook een statische koppeling uitgevoerd.
Om de output van de basispointer, de juiste inhoud en de juiste koppeling te maken, gaan we voor dynamische binding van functies. Dit wordt bereikt met behulp van het virtuele functiemechanisme dat in de volgende sectie wordt uitgelegd.
Virtuele functie
Omdat de overschreven functie dynamisch moet worden gebonden aan de body van de functie, maken we de basisklasse-functie virtueel met behulp van het 'virtuele' trefwoord. Deze virtuele functie is een functie die wordt overschreven in de afgeleide klasse en de compiler voert een late of dynamische binding uit voor deze functie.
Laten we nu het bovenstaande programma wijzigen om het virtuele sleutelwoord als volgt op te nemen:
Uitgang:
Klasse :: Afgeleid
Dus in de bovenstaande klassendefinitie van Base hebben we de functie show_val als 'virtueel' gemaakt. Omdat de basisklasse-functie virtueel wordt gemaakt, wanneer we een afgeleid klasse-object toewijzen aan de basisklasse-pointer en de functie show_val aanroepen, gebeurt de binding tijdens runtime.
Dus, aangezien de basisklasse pointer een afgeleid klasseobject bevat, is de show_val functie body in de afgeleide klasse gebonden aan de functie show_val en dus de output.
In C ++ kan de overschreven functie in de afgeleide klasse ook privé zijn. De compiler controleert alleen het type object tijdens het compileren en bindt de functie tijdens runtime, dus het maakt geen verschil, zelfs niet als de functie openbaar of privé is.
Merk op dat als een functie virtueel wordt gedeclareerd in de basisklasse, deze virtueel zal zijn in alle afgeleide klassen.
Maar tot nu toe hebben we niet besproken hoe virtuele functies precies een rol spelen bij het identificeren van de juiste functie die moet worden gebonden, of met andere woorden, hoe laat binding daadwerkelijk plaatsvindt.
De virtuele functie is tijdens runtime nauwkeurig aan de hoofdtekst van de functie gebonden door het concept van de virtuele tafel (VTABLE) en een verborgen aanwijzer genaamd _vptr.
Beide concepten zijn interne implementatie en kunnen niet rechtstreeks door het programma worden gebruikt.
Werken met virtuele tafel en _vptr
Laten we eerst begrijpen wat een virtuele tafel (VTABLE) is.
De compiler stelt tijdens het compileren één VTABLE in, elk voor een klasse met virtuele functies, evenals de klassen die zijn afgeleid van klassen met virtuele functies.
Een VTABLE bevat items die functie-verwijzingen zijn naar de virtuele functies die kunnen worden aangeroepen door de objecten van de klasse. Er is één vermelding van de functieaanwijzer voor elke virtuele functie.
In het geval van puur virtuele functies is dit item NULL. (Dit is de reden waarom we de abstracte klasse niet kunnen instantiëren).
De volgende entiteit, _vptr die de vtable pointer wordt genoemd, is een verborgen pointer die de compiler toevoegt aan de basisklasse. Deze _vptr verwijst naar de vtable van de klas. Alle klassen die van deze basisklasse zijn afgeleid, erven de _vptr.
Elk object van een klasse die de virtuele functies bevat, slaat deze _vptr intern op en is transparant voor de gebruiker. Elke aanroep van een virtuele functie met behulp van een object wordt vervolgens opgelost met behulp van deze _vptr.
is unix en linux hetzelfde
Laten we een voorbeeld nemen om de werking van vtable en _vtr te demonstreren.
Uitgang:
Derived1_virtual :: function1_virtual ()
Base :: function2_virtual ()
In het bovenstaande programma hebben we een basisklasse met twee virtuele functies en een virtuele destructor. We hebben ook een klasse afgeleid uit de basisklasse en daarin; we hebben slechts één virtuele functie overschreven. In de hoofdfunctie wordt de afgeleide klassenpointer toegewezen aan de basispointer.
Vervolgens noemen we beide virtuele functies met behulp van een basisklasse-aanwijzer. We zien dat de overschreven functie wordt aangeroepen wanneer deze wordt aangeroepen en niet de basisfunctie. Terwijl in het tweede geval, omdat de functie niet wordt overschreven, de basisklasse-functie wordt aangeroepen.
Laten we nu eens kijken hoe het bovenstaande programma intern wordt weergegeven met vtable en _vptr.
Zoals in de eerdere uitleg, aangezien er twee klassen met virtuele functies zijn, zullen we twee vtables hebben - één voor elke klasse. _Vptr zal ook aanwezig zijn voor de basisklasse.
Hierboven ziet u de grafische weergave van hoe de vtable-indeling zal zijn voor het bovenstaande programma. De vtable voor de basisklasse is eenvoudig. In het geval van de afgeleide klasse, wordt alleen function1_virtual overschreven.
Daarom zien we dat in de afgeleide klasse vtable, functie pointer voor function1_virtual verwijst naar de overschreven functie in de afgeleide klasse. Aan de andere kant wijst de functie pointer voor function2_virtual naar een functie in de basisklasse.
Dus in het bovenstaande programma, wanneer de basispointer een afgeleid klasseobject is toegewezen, wijst de basispointer naar _vptr van de afgeleide klasse.
Dus wanneer de aanroep b-> function1_virtual () wordt gedaan, wordt de functie1_virtual van de afgeleide klasse aangeroepen en wanneer de functie-aanroep b-> function2_virtual () wordt gemaakt, aangezien deze functie-pointer naar de basisklasse-functie verwijst, de basisklasse-functie wordt genoemd.
Pure virtuele functies en abstracte klasse
We hebben details over virtuele functies in C ++ gezien in onze vorige sectie. In C ++ kunnen we ook een ' pure virtuele functie ”Dat wordt meestal gelijkgesteld aan nul.
De pure virtuele functie wordt gedeclareerd zoals hieronder weergegeven.
De klasse die ten minste één pure virtuele functie heeft die een ' abstracte klasse We kunnen de abstracte klasse nooit instantiëren, d.w.z. we kunnen geen object van de abstracte klasse creëren.
Dit komt omdat we weten dat er voor elke virtuele functie in de VTABLE (virtuele tabel) een vermelding wordt gemaakt. Maar in het geval van een puur virtuele functie, heeft deze invoer geen adres, waardoor deze onvolledig is. De compiler staat het maken van een object voor de klasse dus niet toe met onvolledige VTABLE-invoer.
Dit is de reden waarom we geen abstracte klasse kunnen instantiëren.
Het onderstaande voorbeeld toont zowel de Pure virtuele functie als de abstracte klasse.
Uitgang:
De puur virtuele functie in de afgeleide klasse overschrijven
In het bovenstaande programma hebben we een klasse gedefinieerd als Base_abstract die een pure virtuele functie bevat die het een abstracte klasse maakt. Vervolgens leiden we een klasse “Derived_class” af van Base_abstract en overschrijven de pure virtuele functie print erin.
In de hoofdfunctie wordt niet die eerste regel becommentarieerd. Dit komt omdat als we het commentaar verwijderen, de compiler een foutmelding geeft omdat we geen object voor een abstracte klasse kunnen maken.
Maar vanaf de tweede regel werkt de code. We kunnen met succes een basisklasse-aanwijzer maken en dan wijzen we er een afgeleid klasse-object aan toe. Vervolgens roepen we een printfunctie aan die de inhoud van de printfunctie uitvoert die overschreven is in de afgeleide klasse.
Laten we in het kort enkele kenmerken van abstracte klasse opsommen:
- We kunnen geen abstracte klasse instantiëren.
- Een abstracte klasse bevat ten minste één puur virtuele functie.
- Hoewel we geen abstracte klasse kunnen instantiëren, kunnen we altijd verwijzingen of verwijzingen naar deze klasse maken.
- Een abstracte klasse kan een aantal implementatie-achtige eigenschappen en methoden hebben, samen met pure virtuele functies.
- Wanneer we een klasse afleiden uit de abstracte klasse, moet de afgeleide klasse alle pure virtuele functies in de abstracte klasse overschrijven. Als dit niet het geval is, is de afgeleide klasse ook een abstracte klasse.
Virtuele vernietigers
Destructors van de klasse kunnen als virtueel worden gedeclareerd. Telkens wanneer we upcast doen, d.w.z. het afgeleide klasse-object toewijzen aan een basisklasse-aanwijzer, kunnen de gewone destructors onaanvaardbare resultaten produceren.
Bijvoorbeeld,overweeg de volgende uitschakeling van de gewone vernietiger.
Uitgang:
Basisklasse :: Destructor
In het bovenstaande programma hebben we een overgeërfde afgeleide klasse van de basisklasse. In het algemeen wijzen we een object van de afgeleide klasse toe aan een basisklasse-pointer.
Idealiter zou de destructor die wordt aangeroepen wanneer 'delete b' wordt aangeroepen, die van de afgeleide klasse moeten zijn, maar we kunnen aan de hand van de uitvoer zien dat de destructor van de basisklasse wordt aangeroepen omdat de basisklasse-aanwijzer daarnaar verwijst.
Hierdoor wordt de afgeleide klasse-destructor niet aangeroepen en blijft het afgeleide klasse-object intact, wat resulteert in een geheugenlek. De oplossing hiervoor is om de constructor van de basisklasse virtueel te maken, zodat de objectpointer naar de correctie van de destructor wijst en de juiste vernietiging van objecten wordt uitgevoerd.
Het gebruik van virtuele destructor wordt getoond in het onderstaande voorbeeld.
Uitgang:
hoe je junit-testcases schrijft in java
Afgeleide klasse :: Destructor
Basisklasse :: Destructor
Dit is hetzelfde programma als het vorige programma, behalve dat we een virtueel sleutelwoord hebben toegevoegd vóór de destructor van de basisklasse. Door de basisklasse destructor virtueel te maken, hebben we de gewenste output bereikt.
We kunnen zien dat wanneer we een afgeleid klasseobject toewijzen aan de basisklasse-pointer en vervolgens de basisklasse-pointer verwijderen, destructors worden aangeroepen in de omgekeerde volgorde van het maken van objecten. Dit betekent dat eerst de afgeleide klasse-destructor wordt aangeroepen en het object wordt vernietigd en vervolgens wordt het basisklasse-object vernietigd.
Notitie: In C ++ kunnen constructeurs nooit virtueel zijn, aangezien constructeurs betrokken zijn bij het construeren en initialiseren van de objecten. Daarom hebben we alle constructeurs nodig om volledig te worden uitgevoerd.
Gevolgtrekking
Runtime-polymorfisme wordt geïmplementeerd met behulp van methode-overschrijving. Dit werkt prima als we de methoden met hun respectievelijke objecten aanroepen. Maar als we een basisklasse-aanwijzer hebben en we aanroepen overschreven methoden met behulp van de basisklasse-aanwijzer die naar de afgeleide klasseobjecten wijst, treden onverwachte resultaten op vanwege statische koppelingen.
Om dit te verhelpen, gebruiken we het concept van virtuele functies. Met de interne weergave van vtables en _vptr helpen virtuele functies ons om de gewenste functies nauwkeurig aan te roepen. In deze zelfstudie hebben we in detail gezien over runtime-polymorfisme dat wordt gebruikt in C ++.
Hiermee sluiten we onze tutorials over objectgeoriënteerd programmeren in C ++ af. We hopen dat deze tutorial nuttig zal zijn om een beter en grondig begrip te krijgen van objectgeoriënteerde programmeerconcepten in C ++.
Bezoek hier om C ++ vanaf het begin te leren.
Aanbevolen literatuur
- Polymorfisme in C ++
- Overerving in C ++
- Vriendfuncties in C ++
- Klassen en objecten in C ++
- Gebruik van Selenium Selecteer klasse voor het omgaan met vervolgkeuzelijsten op een webpagina - Selenium-zelfstudie # 13
- Python Main Function-zelfstudie met praktische voorbeelden
- Java Virtual Machine: hoe JVM helpt bij het uitvoeren van Java-applicaties
- Hoe LoadRunner VuGen-scriptbestanden en runtime-instellingen in te stellen