Axel Rogat
Objektorientiertes Programmieren mit C++ und JAVA
 
17.8: RTTI Kapitel 17 17.10: Beispiele 
 
  17.9 Rein virtuelle Funktionen und abstrakte Klassen  
 

In unserem Shape-Beispiel haben wir die Funktionen rotate, area und realize für die spezielleren Klassen neu definiert. Die allgemeinste Klasse Shape ist eigentlich nicht dazu gedacht, daß von ihr direkt Objekte erzeugt werden können -- es gibt keine "grafischen Objekte" ohne irgendwelche weiteren speziellen Merkmale. Dennoch mußten wir dort irgendeine Standard-Implementation ("dummy definition") angeben, obwohl es keine sinvolle Definition für das Rotieren, den Flächeninhalt und das Zeichnen eines allgemeinen, nicht weiter definierten grafischen Objekts gibt.

In diesem Fall wollten wir von vornherein keine Implementation vererben, sondern nur die Schnittstelle der Funktionen. Wir wollten festlegen, daß spezialisiertere Klassen solche Funktionen bereitstellen müssen, damit sie für alle grafischen Objekte auf die gleiche Weise verfügbar sind.

Um das zu erreichen, ohne irgendeine mehr oder weniger unsinnige Standard-Implementation angeben zu müssen, verwendet man rein virtuelle Funktionen ("pure virtual functions"). Die Deklaration wird dazu einfach durch die Angabe =0 ergänzt:

class Shape { // ... virtual double area() const = 0; };
Wenn eine Klasse (mindestens) eine rein virtuelle Funktion enthält, kann man von ihr keine Objekte erzeugen. Sie heißt dann abstrakt, bzw. abstrakte Basisklasse. Die anderen Klassen werden als Gegensatz dazu als konkret bezeichnet.

Abgeleitete Klassen bleiben abstrakt, bis für alle rein virtuellen Funktionen eine Implementation angegeben wird (die von dort aus weitervererbt werden kann).

Unten ist eine abstrakte Basisklasse für Stacks angegeben. Sie schreibt nur die Schnittstellen vor. Klassen, die von ihr erben, müssen eine Implementation für diese Funktionen angeben, oder die Klassen bleiben weiterhin abstrakt. Unsere alte Klasse könnte problemlos von dieser abgeleitet werden.

template <class T> class stack { public: virtual bool isempty() const = 0; virtual void push(const T&) = 0; virtual T top() const = 0; virtual void pop() = 0; virtual ~stack() = 0; };
Man kann bereits auf diesem abstrakten Level einige Funktionen implementieren, die für alle Stacks so funktionieren müssen, gleichgültig, wie sie physikalisch implementiert werden. Zum Beispiel definieren wir eine Funktion clear zum Leeren des Stacks:
template <class T> class stack { // ... virtual void clear() { while (!isempty()) pop(); } };
Zu beachten ist, daß clear auf der höchsten Ebene definiert ist, ohne daß die verwendeten Funktionen isempty und pop bereits konkretisiert sind. Ein konkreter Erbe von stack verwendet also diese Methode, wobei durch den virtuellen Aufrufmechanismus seine konkreten Implementationen eingesetzt werden!

Je nach Implementation (z.B., wenn der Stack auf einem Array aufbaut), kann es natürlich effizientere Möglichkeiten geben, die diese Standardimplementation dann überschreiben sollten.

Andererseits darf man sich nicht täuschen: Durch Klassen dieser Art wird wirklich nur die Schnittstelle einer Funktion vorgegeben und nichts über ihre Wirkungsweise gesagt. In Eiffel beispielsweise kann man dagegen die Axiome des zugrundeliegenden abstrakten Datentyps im Quellcode angeben, z.B.:

push(x:T) is require -- Vorbedingung: Stack darf nicht voll sein (gedacht not full -- für speicherbeschränkte Stacks mit Arrays) deferred -- "aufgeschoben", "abstrakt" ensure -- Nachbedingung not empty; -- nach einem push ist der Stack nicht mehr leer, top=x -- und top liefert das gerade gepushte Element end
Die konkrete Implementation muß sich dann an diese Vorgaben halten.

 
17.8: RTTI Kapitel 17 17.10: Beispiele 
 

© 1998 Axel Rogat