| Axel Rogat |
| Objektorientiertes Programmieren mit C++ und JAVA |
|   |
18.3: Mehrfachv./Layering
| Kapitel 18 |
19.1: Ausnahmebehandlung 
|
|   |
|   | 18.4 Virtuelle Basisklassen |   |
|   |
Als einfachstes motivierendes Beispiel bringen wir eine mögliche zusätzliche Eigenschaft unserer grafischen Objekte ins Spiel, nämlich ihre Farbe. Wir leiten dazu von der Basisklasse Shape die Klasse ColoredShape ab:
Auch ein farbiges Objekt macht ohne weitere Eigenschaften keinen Sinn -- die Klasse ist wiederum nur zum Ableiten konkreterer Klassen gedacht. Beispielsweise wollen wir mit farbigen Rechtecken arbeiten. Bei einfacher Vererbung müßten wir von Rectangle ableiten und den Code für die Farbe duplizieren oder von ColoredShape ableiten und den Code für die Rechtecke duplizieren. Wir erledigen beides durch Ableiten von beiden Klassen:
| Die Klassenhierarchie, die wir erzeugt haben, ist rechts dargestellt. Die Klasse Shape ist zweimal beim Ableitungsvorgang beteiligt -- durch die beiden Wege über ColoredShape und Rectangle. Ein Objekt vom Typ ColoredRectangle hat dementsprechend zwei anonyme Shape-Subobjekte, insbesondere zwei Referenzpunkte. |
|
Erstens macht das wenig Sinn, und zweitens führt es zu Mehrdeutigkeiten. Die Memberfunktionen, die bereits in Shape definiert sind, wurden doppelt geerbt. Bei Verwendung etwa von moveto() wird jedesmal ein Fehler gemeldet.
Wenn wir unsere abstrakte Shape-Version als Grundlage verwenden, verschlimmert sich die Situation noch, weil ColoredRectangle abstrakt bleibt! Die rein virtuellen Funktionen wie area und rotate aus Shape werden in Rectangle zwar konkretisiert, nicht aber die, die über den ColoredShape-Weg Eingang in unsere Klasse finden. Dementsprechend können wir keine ColoredRectangle-Objekte erzeugen:
| Was wir eigentlich erreichen wollten, war, daß die Eigenschaften der Klasse Shape auf zwei verschiedene Weisen ergänzt werden. Der Graph, den wir realisieren wollen, ist der DAG (directed acyclic graph), der rechts dargestellt ist. Die Stellen, an denen das in C++ eingeht, sind dabei die Deklarationen von ColoredShape und Polygon. |
|
Basisklassen, die in dieser Art vererbt werden sollen, heißen virtuelle Basisklassen. Diese Bezeichnung ist deswegen gerechtfertigt, weil der Mechanismus zum Ansprechend des zugehörigen anonymen Subobjekts dem von virtuellen Funktionen sehr ähnlich ist.
In der Deklaration von ColoredShape und Polygon versehen wir dazu die Basisklassen-Angabe Shape mit dem zusätzlichen Schlüsselwort virtual (vor oder nach public):
Bei der internen Darstellung von Objekten von Klassen mit einer virtuellen Basisklasse ergibt sich ein Problem, das auch direkte Auswirkungen auf den Code hat: Es darf in Rectangle nur ein Shape-Subobjekt geben, aber das muß sowohl zum ColoredShape-Subobjekt wie auch zum Polygon-Subobjekt gehören.
Auf den Code bezogen bedeutet das: Sowohl das ColoredShape-Subobjekt wie auch das Polygon-Subobjekt initialisieren ihr Shape-Subobjekt. Wenn die beiden Shapes zu einem zusammenfallen, wie wird dieses nun initialisiert -- der Reihe nach durch Überschreiben, durch die erste oder durch die letzte Klasse in der Initialisiererliste?
Intern wird zur Darstellung der Aufbau der Subobjekte verändert, in denen die Basisklasse als virtuell deklariert wurde. Statt des Subobjekts der Basisklasse findet man dort nur einen Pointer bptr auf das eigentliche Subobjekt, das an der Stelle des Gesamtobjekts untergebracht ist, an der im Graphen die beiden Äste wieder zusammenkommen.
In unserem Beispiel sieht ein ColoredRectangle-Objekt also in etwa wie folgt aus (der genaue Aufbau kann von Compiler zu Compiler variieren):
Wenn man einen Pointer auf ein ColoredRectangle-Objekt erzeugt und mit dynamischen Cast-Operationen darauf arbeitet, erhält man die tatsächlichen Adressen der einzelnen Subobjekte, z.B.:
Hier sieht man auch, warum es nötig ist, die Virtualität in den Klassen ColoredShape und Polygon zu definieren: Die Pointer-Struktur darf nicht erst im letzten Schritt eingesetzt werden. Damit ein solcher Aufbau funktioniert, müssen alle Objekte dieser Klassen den verpointerten Aufbau haben, auch, wenn sie nicht als Subobjekte von ColoredRectangle verwendet werden.
Es bleibt noch das Problem der Initialisierung des mehrfach eingebetteten Subobjekts (bei uns Shape) zu lösen.
Im Bild oben gibt es also nur ein vollständiges Objekt, nämlich das ColoredRectangle-Objekt als ganzes. Es ist das vollständige Objekt der anderen vorkommenden Objekte.
Die Basisklasse im Beispiel oben ist Shape. Die bezüglich dazu am weitesten abgeleitete Klasse ist ColoredRectangle.
Um die Initialisierung des Basisklassen-Teilobjekts eindeutig zu machen, hat man folgendes festgelegt:
In der am weitesten abgeleiteten Klasse sollte also explizit ein Konstruktor der Basisklasse aufgerufen werden. Ansonsten wird automatisch versucht, den Standard-Konstruktor der Basisklasse zu verwenden.
In unserem Fall müssen wir also explizit einen Shape-Konstruktor aufrufen -- in allen Klassen von Polygon ab abwärts. Polygon erbt direkt von Shape, so daß hier nichts zu ändern ist. Zusätzliche Aufrufe gehören aber nun nach Rectangle, Square und Triangle:
Ähnlich sehen Square und Triangle aus:
| Die Klasse checked_arith_array sollte die Index-Funktionalität von checked_array und die Summen-Funktion von arith_array bieten. Sie durfte aber nicht von beiden Klassen erben, da sie sonst nicht nur deren Funktionalität, sondern auch ihre Datenmember geerbt hätte -- insbesondere also zwei interne Arrays! |
|
Zunächst müssen wir das Schlüsselwort virtual in der Definition der beiden Zwischenklassen einfügen (ansonsten wird bei ihnen nichts verändert):
Zu beachten ist, daß der Index-Operator nicht zweimal geerbt wird. Es gibt zwar in beiden direkten Oberklassen eine Version von ihm, aber die aus arith_array ist aus array übernommen. Diese gilt als überschrieben durch die Version aus checked_array. Es wird also die gecheckte Version verwendet.
Nur, wenn auch in arith_array eine neue Version von [] definiert wäre, gäbe es Mehrdeutigkeiten. Der Operator müßte dann in checked_arith_array neu definiert werden (er könnte dann natürlich auf eine der Oberklassen-Versionen verweisen).
|   |
18.3: Mehrfachv./Layering
| Startseite |
19.1: Ausnahmebehandlung 
|
|   |