Programmieren in C++
Kapitel 5: Polymorphie

5.1 Was bedeutet Polymorphie?

Stellen wir uns vor, wir hätten eine Klasse A und leiten davon jeweils eine Klasse B und C ab. Jetzt wollen wir ein Feld oder eine Liste anlegen, die Zeiger auf Objekte von B und C enthält. Eine denkbare Anwendung wäre, dass A eine Vorlage für geometrische Objekte allgemein ist und B und C konkrete Formen wie Kreis oder Rechteck sind. Es macht keine Probleme solche Klassen allgemein zu definieren und abzuleiten. Wir rufen einfach zunächst die Funktionen aus der Basisklasse auf, bevor wir auf die Funktionen der abgeleiteten Klassen zurückgreifen. Wie aber legen wir Felder über Bs und Cs an? Angenommen, jede Klasse A, B und C hat eine eigene Funktion drucke():

A* feld[2];
B b;
C c;
feld[0] = &b;
feld[1] = &c;

feld[0]->drucke(); feld[1]->drucke();

Der Compiler erlaubt uns zwar die Typkonversion von "Zeiger auf B" in "Zeiger auf A", im entscheidenden Moment aber (wenn wir die Funktion drucke aufrufen) sieht er nur einen "Zeiger auf A" und ruft die zu A gehörige Funktion auf. Eine Tatsache, die uns das vernünftige Arbeiten mit solchen Feldern unmöglcih macht. Leider ist es aber in der Praxis seht häufig erforderlich, Listen oder Felder über mehrere Objekte unterschiedlicher abgeleiteter Klassen aufzumachen.

Bei diesem Problem helfen uns virtuelle Funktionen weiter. Wir definieren in A die Funktion drucke() als virtuell und teilen dem dadurch Compiler mit, dass er sich bitte darum zu kümmern hat, dass beim Aufruf der Funktion ggf. die Funktion aus der jeweils abgeleiteten Klasse zum Zug kommt:

class A
{
public:
virtual void
drucke() { cout << "So druckt A" << endl; };
};

class B: public A
{
public:
void
drucke() { cout << "So druckt B" << endl; };
};

class C: public A
{
public:
void
drucke() { cout << "So druckt C" << endl; };
};

void main()
{
A* feld[2];
B b;
C c;
feld[0] = &b;
feld[1] = &c;

feld[0]->drucke(); feld[1]->drucke();
}

Das Programm merkt sich also den Typ, auf den der Zeiger im Feld jeweils zeigt und verwendet die Funktion der abgeleiteten Klasse. Man spricht in diesem Fall von später Bindung. Wird eine virtuelle Funktion in der abgeleiteten Klasse nicht definiert, tritt die Basisklassenfunktion an ihre Stelle. Deshalb müssen wir hier immer public ableiten! Polymorphie funktioniert nur mit Zeigern. Es wäre nicht möglich, ein Feld von Objekten verschiedener Klassen aufzumachen.

Die Deklaration einer Funktion als virtual hindert uns nicht daran, trotzdem die Basisversion der Funktion mitzuverwenden. Über den Scope-Operator :: können wir nach wie vor schreiben: void drucke() { A::drucke(); cout << "So druckt B" << endl; };


5.2 Rein virtuelle Funktionen - Abstrakte Basisklassen

Rein virtuelle Funktionen sind solche die in der Basisklasse zwar deklariert aber nicht definiert werden. Eine Flächenberechnung einer Klasse CForm ist erst möglich, wenn wir wissen, um welche Form es sich handelt. CKreis berechnet seine Fläche anders als CRechteck. Um aber eine Liste aus Formen zu erstellen, muss die Klasse CForm schonmal wissen, das es eine Flächenberechnungsfunktion gibt. Es gnügt jetzt nicht, eine Deklaration von flaeche() in die Klasse CForm aufzunehmen. Dies würde uns ja dazu zwingen, gleich eine Definition nachzureichen - der Linker will das so. Eine Definition macht an dieser Stelle ja aber noch gar keinen Sinn.

Eine rein virtuelle Funktion wird wie folgt definiert:

virtual double flaeche() = 0;

Wir machen durch ein = 0 am Ende der Deklaration klar, dass wir in dieser Klasse keine Definition einbringen werden. Jetzt bleibt uns aber keine andere Wahl: wir müssen in allen abgeleiteten Klassen die Funktion flaeche definieren. Außerdem können wir keine Objekte mehr vom Typ der Basisklasse anlegen. Wie sollte der Compiler auch die Funktion flaeche auf der Basisklasse interpretieren??

Abstrakte Basisklassen haben mindestens eine rein virtuelle Member-Funktion. Da sie nicht instanziert werden können, sollte man die Konstruktoren in den protected-Teil der Klasse ziehen, so dass eine versehentliche Instanzierung verhindert wird. Abgeleitete Klassen können diese dann immer noch explizit aufrufen.

[ Beispiel ]


© Gerhard Zapf
Vererbung
Sitemap
www.tutorialpage.de
Templates
Diese Seite wurde seit dem 12.07.01 genau 168 mal abgerufen.
Letzte Änderung: 12.01.2006