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.