Virtuelle Funktionen werden erst zur Laufzeit, also nicht bereits zum Kompilierzeitpunkt, an eine Klasse gebunden. Das Programm entscheidet, welche Funktion für ein spezielles Objekt gewählt werden muss. Dadurch kann zur Laufzeit die richtige Version einer abgeleiteten Klasse aufgerufen werden. Man deklariert eine virtuelle Funktion in der Basisklasse. In abgeleiteten Klassen kann diese überschrieben werden. Entscheidend dabei ist, dass man mit einem Zeiger vom Typ der Basisklasse, der auf ein Objekt der abgeleiteten Klasse zeigt, automatisch die richtige Methode, also die der abgeleiteten Klasse und nicht die der Basisklasse, aufruft. Virtuelle Funktionen gehorchen somit dem Prinzip der „späten Bindung“. Für virtuelle Funktionen werden in C++ sogenannte V-Tables (virtual function table) verwendet.
Schauen wir uns an einigen Beispielen an, wie dieser wichtige Mechanismus funktioniert. Im nachfolgenden Programm verfügen wir über eine Basisklasse, zwei davon abgeleitete Kindklassen und zwei davon jeweils abgeleitete Enkelklassen. Alle Klassen besitzen die Funktion virt_function(), die unter gleichem Namen "vielgestaltig" (polymorph) implementiert ist:
#include
<iostream>
using
namespace std;
class
Basisklasse
{
public:
virtual void virt_function() { cout << "Basis sagt Hallo"
<< endl; };
};
class
Kindklasse1 : public Basisklasse
{
public:
void
virt_function() { cout << "Kind1 sagt Hallo" << endl; }
};
class
Kindklasse2 : public Basisklasse
{
public:
void
virt_function() { cout << "Kind2 sagt Hallo" << endl; }
};
class
Enkelklasse1 : public Kindklasse1
{
public:
void
virt_function() { cout << "Enkel1 sagt Hallo" << endl; }
};
class
Enkelklasse2 : public Kindklasse2
{
public:
void
virt_function() { cout << "Enkel2 sagt Hallo" << endl; }
};
int main()
{
Basisklasse
basis;
Kindklasse1
kind1;
Kindklasse2
kind2;
Enkelklasse1 enkel1;
Enkelklasse2 enkel2;
Basisklasse *pZeiger = &basis;
pZeiger->virt_function();
pZeiger = &kind1;
pZeiger->virt_function();
pZeiger = &kind2;
pZeiger->virt_function();
pZeiger = &enkel1;
pZeiger->virt_function();
pZeiger = &enkel2;
pZeiger->virt_function();
return 0;
}
In dem vorstehenden Programm wird für jedes Objekt die richtige Version der Funktion virt_function aufgerufen. Bei virtuellen Funktionen entscheidet nicht der Typ des Zeigers, sondern der Typ des Objekts über die verwendete Funktion. Damit klar wird, das dies durch den Bezeichner "virtual" ausgelöst wird, lassen wir ihn im nachfolgenden Beispiel versuchsweise weg:
class
Basisklasse
{
public:
/*virtual*/ void
virt_function() { cout
<< "Basis sagt Hallo" << endl; };
};
Nun wird für alle Objekte unabhängig vom Typ immer die Funktion der Basisklasse aufgerufen. Eine „späte Bindung“ in Abhängigkeit vom Objekttyp erfolgt nicht. Ohne virtuelle Funktion gelingt dies nur mit dem richtigen Objekttyp:
int
main()
{
...
basis.virt_function();
kind1.virt_function();
kind2.virt_function();
enkel1.virt_function();
enkel2.virt_function();
return 0;
}
... oder mit Zeigern auf die jeweilige abgeleitete Klasse:
int
main()
{
...
(&basis)->virt_function();
(&kind1)->virt_function();
(&kind2)->virt_function();
(&enkel1)->virt_function();
(&enkel2)->virt_function();
return 0;
}
Nachfolgend noch einige Hinweise zu virtuellen Funktionen:
Soll die virtuelle Funktion in einer abgeleiteten Klasse überschrieben, also neu definiert, werden, müssen Typ des Rückgabewertes und Zahl und Art der Parameter genau übereinstimmen.
Die Kennzeichnung „virtual“ kann in den abgeleiteten Klassen entfallen.
Nicht jede abgeleitete Klasse muss eine virtuelle Funktion redefinieren.