Problem bei Mehrfach-Ableitung
Virtuelle Basisklassen
Virtuelle Basisklassen und Konstruktore
Beispiel
Um die Bedeutung von virtuellen Basisklassen zu demonstrieren, sehen Sie sich einmal die nachfolgende Klassenhierarchie an. Oberste Basisklasse ist die Klasse CWinBase. Davon abgeleitet werden die beiden Klassen CFrame (Implementierung eines Fensters mit Rahmen) und die Klasse CMenu (Implementierung eines Fenstermenüs). Aus diesen beiden Klassen wird dann eine neue Klasse CWindow gebildet, die ein Rahmenfenster mit Menü darstellen soll.

Wenn Sie den Kurs bisher aufmerksam durchgearbeitet haben, dann sollten Sie jetzt leichte Zweifel an dieser Klassenhierarchie haben. Da sowohl CFrame wie auch CMenu von CWinBase abgeleitet sind, erben diese beiden Klassen natürlich die Eigenschaften von CWinBase. Wird nun aus CFrame und CMenu eine neue Klasse gebildet, so enthält die neue Klasse die Eigenschaften von CWinBase in zweifacher Ausführung, einmal von CFrame und einmal von CMenu. Und das ist bestimmt nicht das, was mit der Ableitung erreicht werden sollte. Das nachfolgende Bild veranschaulicht diesen Sachenverhalt nochmals.

Und auch in diesen Fall hilft uns das Schlüsselwort virtual weiter. Wird eine Klasse von mehreren Basisklassen abgeleitet, die wiederum selbst von einer gemeinsamen Basisklasse abgeleitet sind, so kann durch virtuelles Ableiten von der Basisklasse vermieden werden, dass die Eigenschaften der obersten Basisklasse mehrfach weitervererbt werden. In unserem Beispiel müssen also die Klassen CFrame und CMenu virtuell von CWinBase abgeleitet werden.

Sehen wir uns dieses virtuelle Ableiten in der Praxis an. Um eine Klasse virtuelle abzuleiten, wird vor dem Zugriffrecht der Ableitung das Schlüsselwort virtual gestellt. Beachten Sie, dass nur CFrame und CMenu virtuell abgeleitet sind und nicht mehr die endgültige Klasse CWindow.
// Oberste Basisklasse class CWinBase {....}; // 1. virtuell abgeleitete Klasse class CFrame: virtual public CWinBase {....}; // 2. virtuell abgeleitete Klasse class CMenu: virtual public CWinBase {....}; // Endgültige Klasse class CWindow: public CFrame, public CMenu {....}; |
Eine Besonderheit beim virtuellen Ableiten müssen Sie jedoch beachten. Bei einer 'normalen' Ableitung ruft der Konstruktor einer abgeleiteten Klassen die Konstruktoren seiner Basisklassen auf. Sie können Sie sich diesen Sachverhalt jetzt nochmals hier ansehen. Dieser Sachverhalt trifft bei virtuell abgeleiteten Klassen aber nur dann zu, wenn die Basisklassen einen Standard-Konstruktor besitzen. Benötigt der Konstruktor der Basisklasse jedoch Parameter, so ist der Konstruktor der abgeleiteten Klasse selbst dafür verantwortlich, den Konstruktor der Basisklasse aufzurufen. Für unser Beispiel ergibt sich damit der unten dargestellte Konstruktor der Klasse CWindow.
CWindow::CWindow(...): CWinBase(...), CFrame(...), CMenu(...) // Aufrufe der ctors { .... } |
Für die Aufruf-Reihenfolge der Konstruktoren gilt, dass zuerst der Konstruktor der obersten Basisklasse ausgeführt wird und danach die Konstruktoren der nachfolgenden Basisklassen und zwar in der Reihenfolge, in der die Basisklassen bei der Klassendefinition angegeben wurden. Halten Sie diese Reihenfolge auch bei der Initialisiererliste der 'untersten' Klasse ein, da Sie ansonsten je nach Compiler eventuell eine Reihe Warnungen erhalten.
|
|
--- Definition CBar --- |
// Beispiel zu virtuellen Basisklassen // Zuerst Dateien einbinden #include <iostream> #include <string> using std::cout; using std::endl; using std::string; // Definition der Klasse CGraphic // CGraphic ist eine abstrakte Klasse da sie die 'pure virtual' // Memberfunktion Draw() enthält. // CGraphic speichert die allen Grafikelementen gemeinsamen // X/Y Koordinaten. class CGraphic { protected: short nXPos, nYPos; // Koordinaten der Grafik CGraphic(short nX, short nY); // protected-Konstruktor public: virtual ~CGraphic() // virtueller dtor! {} virtual void Draw() const = 0; }; // Definition der Memberfunktionen // Konstruktor CGraphic::CGraphic(short nX, short nY) { // Koordinaten der Grafik ablegen nXPos = nX; nYPos = nY; cout << "Konstruktor CGraphic\n"; } // Definition der Klasse CCircle // CCircle ist abgeleitet von CGraphic und enthält als // zusätzliches Datum den Radius eines Kreises class CCircle: virtual public CGraphic { short nRadius; // Kreisradius public: CCircle (short, short, short); void Draw() const; }; // Definition der Memberfunktionen // Konstruktor CCircle::CCircle(short nX, short nY, short nR): CGraphic(nX, nY) // Konstruktor Basisklasse { // Radis ablegen cout << "Konstruktor CCircle\n"; nRadius = nR; } // Gibt die Kreisdaten aus void CCircle::Draw() const { cout << "Kreis-Parameter\n"; cout << "Position: " << nXPos << "," << nYPos << endl; cout << "Radius : " << nRadius << endl; } // Definition der Klasse CBar // CBar ist abgeleitet von CGraphic und enthält als // zusätzliches Datum die Breite und Höhe des Rechtecks class CBar: virtual public CGraphic { short nWidth, nHeight; // Grösse des Rechtecks public: CBar (short, short, short, short); void Draw() const; }; // Definition der Memberfunktionen // Konstruktor der Klasse CBar CBar::CBar(short nX, short nY, short nW, short nH): CGraphic(nX, nY) // Konstruktor Basisklasse { // Ausdehnung ablegen cout << "Konstruktor CBar\n"; nWidth = nW; nHeight = nH; } // Gibt die Daten des Rechtecks aus void CBar::Draw() const { cout << "Rechteck-Parameter\n"; cout << "Position: " << nXPos << "," << nYPos << endl; cout << "Grösse : " << nWidth << "," << nHeight << endl; } // Definition der Klasse CCBar // CCBar ist abgeleitet von CCircle und CBar und damit auch // von CGraphic class CCBar: public CCircle, public CBar { string sText; public: CCBar(short, short, short, const char *const); void Draw() const; }; // Definition der Memberfunktionen // Konstruktor von CCBar // Beachten Sie die Aufrufe der Konstruktoren der Basisklassen. // Obwohl CCBar nicht direkt von CGraphic abgeleitet ist muß der // Konstruktor von CGraphic gesondert aufgerufen werden CCBar::CCBar(short nX, short nY, short nW, const char *const pszT): CGraphic(nX, nY), // ctor virtuelle Basisklasse CCircle(nX, nY, nW>>1), // ctor Basisklasse CBar(nX, nY, nW, nW), // ctor Basisklasse sText(pszT) // string initialisieren { cout << "Konstruktor CCBar\n"; } // Gibt Daten des beschrifteten Rechtecks aus // Ruft die Memberfunktionen Draw() der Klassen CBar und CCircle auf // Beachten Sie, daß unbedingt die Angabe des Zugriffsspezifizieres // beim Aufruf der Memberfunktionen notwendig ist void CCBar::Draw() const { cout << "CCBar besteht aus:\n"; CCircle::Draw(); CBar::Draw(); cout << "Beschriftung: " << sText << endl; } // main() Funktion int main() { // Feld für 3 Zeiger auf Grafikobjekte definieren // Das Feld nimmt Basisklassenzeiger auf! CGraphic *pCObject[3]; // Kreis erstellen und Zeiger ablegen cout << "--- Definition CBar ---\n"; pCObject[0] = new CBar(10,10,20,20); // Rechteck erstellen und Zeiger ablegen cout << "--- Definition CCircle ---\n"; pCObject[1] = new CCircle(40,40,20); // Beschriftetes Rechteck erstellen und Zeiger ablegen cout << "--- Definition CCBar ---\n"; pCObject[2] = new CCBar(50,50,100,"Rechteck mit Text"); // Alle Grafiken ausgeben und dann wieder löschen for (short iIndex=0; iIndex<3; iIndex++) { pCObject[iIndex]->Draw(); delete pCObject[iIndex]; } } |
Auf eine Übung wird an dieser Stelle verzichtet!