Virtuelle Funktionen lösen obige Probleme, indem der Compiler Code erzeugt, der zur Laufzeit die zum jeweiligen Objekt ``passenden'' Elementfunktionen bestimmt.
Dazu wird eine Funktion, die in einer Unterklasse (evtl.) redefiniert werden soll, in der Basisklasse als virtual gekenzeichnet.
class uhr { int std;int min; public: uhr(int s, int m) : std(s), min(m) {}; uhr() : std(0), min(0) {}; uhr(const uhr& ori) : std(ori.std), min(ori.min) {} virtual void show() { cout << std << ':' << min;} }; class wecker : public uhr { uhr alarmzeit; public: wecker() : alarmzeit(0,0) {} wecker(int s, int m, int as, int am) : uhr(s,m), alarmzeit(as,am) {} wecker(const wecker & ori) : uhr(ori), alarmzeit(ori.alarmzeit) {} void show() { uhr::show(); cout << " || "; alarmzeit.show(); } }; int main() { uhr *up = new wecker(11,30,7,15); up->show(); // ruft wecker::show() }
Jede Klasse, die virtuelle Funktionen benutzt, hat eine Tabelle VFT (virtual function table) mit Zeigern auf den Code der virtuellen Funktionen. Jedes Objekt einer solchen Klasse enthält einen Zeiger auf die VFT seiner Klasse:
Zur Laufzeit des Programms wird die aufzurufende Funktion durch Zugriff auf die virtuelle Funktionstabelle bestimmt (Dynamic Binding).
Dynamische Bindung ist eines der grundlegenden Merkmale objektorientierter Programmiersprachen.
class sigma { double sum; public: sigma() { sum = 0.0;} virtual void input(double d) { sum += d;} double getsum() { return sum;} }; class mue : public sigma { int number; public: mue() : sigma() { number=0;} void input(double d) { sigma::input(d); number++; } double getavg() { return number? getsum()/number:0;} }; int main() { sigma s; mue m; sigma* ps1; for (int i=1; i <= 10; i++) { i%2 ? (ps1=&m) : (ps1=&s); ps1->input(i); } cout << s.getsum() << endl; cout << m.getsum() << endl; }
uhr *uz = new wecker(16,0,17,30); ... delete uz; // FALSCH
benutzt den Destruktor von uhr.
Ausweg: Virtuelle Destruktoren
class uhr { int std; int min; public: uhr(int s, int m) : std(s), min(m) {}; virtual ~uhr(){cout << "~uhr ";} }; class wecker : public uhr { uhr alarmzeit; public: wecker(int s, int m, int as, int am) : uhr(s,m), alarmzeit(as,am) {} ~wecker() { cout << "~wecker ";} }; int main() { uhr *uz = new wecker(16,0,17,30); delete uz; }
schreibt
~wecker ~uhr ~uhr
Virtuelle Funktionen in der obersten Basisklasse werden häufig nie benutzt, weil sie dort keinen Sinn ergeben. Man kann dies ausdrücken, indem man sie als pure virtuelle Funktion kennzeichnet:
class ding { public: // eine pure virtuelle Funktion: virtual int weile() = 0; }; class gutding : public ding { public: int weile() { return 1000000;} }; class schlechtding : public ding { public: int weile() { return 0;} };
Eine Klasse, die mindestens eine pure virtuelle Funktion hat, heißt abstrakte Klasse.
Es ist natürlich verboten, Objekte von abstrakten Klassen zu definieren.
Eine abgeleitete Klasse muß die pure virtuelle Funktion definieren oder sie wiederum als pur kennzeichnen.