Een virtuele functie (ook wel virtuele methoden genoemd) is een lidfunctie die binnen een basisklasse wordt gedeclareerd en opnieuw wordt gedefinieerd (overschreven) door een afgeleide klasse. Wanneer u naar een afgeleid klassenobject verwijst met behulp van een pointer of een verwijzing naar de basisklasse, kunt u een virtuele functie voor dat object aanroepen en de afgeleide klasseversie van de methode uitvoeren.
- Virtuele functies zorgen ervoor dat de juiste functie wordt aangeroepen voor een object, ongeacht het type referentie (of pointer) dat voor de functieaanroep wordt gebruikt.
- Ze worden voornamelijk gebruikt om Runtime-polymorfisme te bereiken.
- Functies worden gedeclareerd met een virtueel trefwoord in een basisklasse.
- Het oplossen van een functieaanroep gebeurt tijdens runtime.
Regels voor virtuele functies
De regels voor de virtuele functies in C++ zijn als volgt:
- Virtuele functies kunnen niet statisch zijn.
- Een virtuele functie kan een vriendfunctie van een andere klasse zijn.
- Virtuele functies moeten toegankelijk zijn met behulp van een pointer of referentie van het basisklassetype om runtime-polymorfisme te bereiken.
- Het prototype van virtuele functies moet hetzelfde zijn in zowel de basisklasse als de afgeleide klasse.
- Ze worden altijd gedefinieerd in de basisklasse en overschreven in een afgeleide klasse. Het is niet verplicht voor de afgeleide klasse om de virtuele functie te overschrijven (of opnieuw te definiëren). In dat geval wordt de basisklasseversie van de functie gebruikt.
- Een klasse kan een virtuele destructor hebben, maar geen virtuele constructor.
Compileertijd (vroege binding) versus runtime-gedrag (late binding) van virtuele functies
Beschouw het volgende eenvoudige programma dat het runtimegedrag van virtuele functies laat zien.
C++
// C++ program to illustrate> // concept of Virtual Functions> #include> using> namespace> std;> class> base {> public>:> >virtual> void> print() { cout <<>'print base class
'>; }> >void> show() { cout <<>'show base class
'>; }> };> class> derived :>public> base {> public>:> >void> print() { cout <<>'print derived class
'>; }> >void> show() { cout <<>'show derived class
'>; }> };> int> main()> {> >base* bptr;> >derived d;> >bptr = &d;> >// Virtual function, binded at runtime> >bptr->print();> >// Non-virtual function, binded at compile time> >bptr->toon();> >return> 0;> }> |
>
>Uitvoer
print derived class show base class>
Uitleg: Runtime-polymorfisme wordt alleen bereikt via een pointer (of referentie) van het basisklassetype. Ook kan een basisklasse-pointer zowel naar de objecten van de basisklasse verwijzen als naar de objecten van de afgeleide klasse. In de bovenstaande code bevat de basisklasse-pointer ‘bptr’ het adres van object ‘d’ van de afgeleide klasse.
Late binding (Runtime) wordt gedaan in overeenstemming met de inhoud van de pointer (d.w.z. locatie waarnaar wordt verwezen door de pointer) en Early binding (Compile-time) wordt gedaan in overeenstemming met het type pointer, aangezien de functie print() wordt gedeclareerd met de virtuele trefwoord zodat het tijdens runtime gebonden wordt (uitvoer is afgeleide klasse afdrukken aangezien de aanwijzer naar een object van een afgeleide klasse wijst) en show() niet-virtueel is, dus zal deze tijdens het compileren gebonden zijn (uitvoer is basisklasse weergeven aangezien de aanwijzer van het basistype is).
Opmerking: Als we een virtuele functie in de basisklasse hebben gemaakt en deze wordt overschreven in de afgeleide klasse, hebben we geen virtueel trefwoord nodig in de afgeleide klasse; functies worden automatisch beschouwd als virtuele functies in de afgeleide klasse.
Werking van virtuele functies (concept van VTABLE en VPTR)
Zoals hier besproken, doet de compiler zelf twee dingen als een klasse een virtuele functie bevat.
- Als een object van die klasse wordt gemaakt, dan wordt a virtuele aanwijzer (VPTR) wordt ingevoegd als een gegevenslid van de klasse om naar de VTABLE van die klasse te verwijzen. Voor elk nieuw object dat wordt gemaakt, wordt een nieuwe virtuele aanwijzer ingevoegd als gegevenslid van die klasse.
- Ongeacht of het object is gemaakt of niet, de klasse bevat het als lid een statische array van functieaanwijzers genaamd VTABLE . Cellen van deze tabel slaan het adres op van elke virtuele functie in die klasse.
Beschouw het onderstaande voorbeeld:
verborgen apps

C++
// C++ program to illustrate> // working of Virtual Functions> #include> using> namespace> std;> class> base {> public>:> >void> fun_1() { cout <<>'base-1
'>; }> >virtual> void> fun_2() { cout <<>'base-2
'>; }> >virtual> void> fun_3() { cout <<>'base-3
'>; }> >virtual> void> fun_4() { cout <<>'base-4
'>; }> };> class> derived :>public> base {> public>:> >void> fun_1() { cout <<>'derived-1
'>; }> >void> fun_2() { cout <<>'derived-2
'>; }> >void> fun_4(>int> x) { cout <<>'derived-4
'>; }> };> int> main()> {> >base* p;> >derived obj1;> >p = &obj1;> >// Early binding because fun1() is non-virtual> >// in base> >p->fun_1();> >// Late binding (RTP)> >p->fun_2();> >// Late binding (RTP)> >p->fun_3();> >// Late binding (RTP)> >p->fun_4();> >// Early binding but this function call is> >// illegal (produces error) because pointer> >// is of base type and function is of> >// derived class> >// p->fun_4(5);> >return> 0;> }> |
>
>Uitvoer
base-1 derived-2 base-3 base-4>
Uitleg: In eerste instantie creëren we een pointer van het type basisklasse en initialiseren we deze met het adres van het afgeleide klasseobject. Wanneer we een object van de afgeleide klasse maken, creëert de compiler een pointer als gegevenslid van de klasse die het adres van VTABLE van de afgeleide klasse bevat.
Een soortgelijk concept van Late en vroege binding wordt gebruikt zoals in het bovenstaande voorbeeld. Voor de functieaanroep fun_1() wordt de basisklasseversie van de functie aangeroepen, fun_2() wordt overschreven in de afgeleide klasse, dus de afgeleide klasseversie wordt aangeroepen, fun_3() wordt niet overschreven in de afgeleide klasse en is een virtuele functie dus de basisklasseversie wordt aangeroepen, op dezelfde manier wordt fun_4() niet overschreven, dus wordt de basisklasseversie aangeroepen.
Opmerking: fun_4(int) in de afgeleide klasse verschilt van de virtuele functie fun_4() in de basisklasse omdat prototypen van beide functies verschillend zijn.
Beperkingen van virtuele functies
- Langzamer: De functieaanroep duurt iets langer vanwege het virtuele mechanisme en maakt het moeilijker voor de compiler om te optimaliseren, omdat deze niet precies weet welke functie tijdens het compileren zal worden aangeroepen. Moeilijk te debuggen: In een complex systeem kunnen virtuele functies het iets moeilijker maken om erachter te komen waar een functie vandaan wordt aangeroepen.