C++ Kurs

Mehrfach abgeleitete Klassen

Die Themen:

Grundprinzip der Mehrfach-Ableitung
Konstruktor und Destruktor
Gleichnamige Member in den Basisklassen
Beispiel

Grundprinzip der Mehrfach-Ableitung

Bisher wurde eine Klasse immer nur von einer Klasse direkt abgeleitet. C++ bietet aber auch die Möglichkeit, eine Klasse von mehreren Basisklassen abzuleiten. Die Anzahl der Basisklassen ist nicht begrenzt, jedoch sollten Sie immer versuchen ein Klasse von so wenig wie möglichen Basisklassen abzuleiten. Ansonsten geht leicht der Überblick verloren. Unten ist anhand eines kleinen Bildes das Prinzip der mehrfachen Ableitung dargestellt. Die Klasse CMixed vereint zunächst alle Eigenschaften/Memberfunktionen der Klassen CBase1 und CBase2 und fügt eventuell eigene hinzu.

 

Die Ableitung einer Klasse von mehreren Basisklassen erfolgt analog dem Ableiten von einer Basisklasse, nur werden jetzt bei der Definition der abgeleiteten Klasse mehrere Basisklassen, einschließlich deren Zugriffsrechte, angegeben. Die einzelnen Basisklassen werden dabei durch Komma voneinander getrennt.


// 1. Basisklasse
class CBase1
{....}; 
// 2. Basisklasse
class CBase2
{....};

// Neue abgeleitete Klasse
class CMixed: public CBase1, protected CBase2
{....}

Konstruktor und Destruktor

Außer der Klassendefinition muss nun selbstverständlich auch der Konstruktor der abgeleiteten Klasse entsprechend angepasst werden. Er muss jetzt alle Konstruktore seiner Basisklassen aufrufen. Dazu wird, wie bei der einfachen Ableitung, nach der Parameterklammer zunächst ein Doppelpunkt angegeben und danach werden alle Konstruktore der Basisklassen aufgelistet. Einzige Ausnahme: Eine Basisklasse besitzt einen Standard-Konstruktor (parameterloser Konstruktor). In diesem Fall erfolgt der Aufruf automatisch.


// ctor der abgeleiteten Klasse
CMixed::CMixed(P1,P2,...):
   CBase1(a1,a2,...),
   CBase2(b1,b2,...)
{
    ....
}

Die Konstruktore der Basisklassen werden in der Reihenfolge abgearbeitet, in der die Basisklassen bei der Definition der abgeleiteten Klasse aufgeführt wurden. Die Reihenfolge der Konstruktoraufrufe bei der Definition des Konstruktors der abgeleiteten Klasse spielt keine Rolle, sie muss nicht einmal zwingend mit der Reihenfolge bei der Deklaration übereinstimmen. Im Beispiel unten wird also zuerst der Konstruktor der Klasse CBase1 ausgeführt und danach der Konstruktor der Klasse CBase2. Allerdings sollte bei sauberer Programmierung diese Reihenfolge keine Rolle spielen dürfen, da beide Basisklassen voneinander unabhängig sind.


// Definition der abgeleiteten Klasse
class CMixed: public CBase1, protected CBase2
{....}
// Definition des ctors
CMixed::CMixed(P1,P2,...):
   CBase2(a1,a2,...),
   CBase1(b1,b2,...)
{
    ....
}

Und auch für die Abarbeitung der Destruktoren gilt das Gleiche wie bei einfach abgeleiteten Klassen: die Destruktoren werden in umgekehrter Reihenfolge ausgeführt wie die Konstruktore.

Gleichnamige Member in den Basisklassen

Etwas acht geben müssen Sie, wenn die Basisklassen untereinander Memberfunktionen mit gleichen Namen enthalten oder sogar die abgeleitete Klasse eine Memberfunktion mit gleichem Namen besitzt wie die Basisklassen. In diesem Fall müssen Sie wieder den Klassennamen vor dem Aufruf der Memberfunktion stellen. Dies gilt sogar auch dann, wenn die Memberfunktionen unterschiedliche Parameter besitzen. Wie Sie bestimmt noch aus der vorherigen Lektion wissen, funktioniert die Sache mit dem Überladen von Memberfunktionen nur innerhalb einer Klasse und nicht über Klassengrenzen hinweg! Vergessen Sie einmal die Klasse explizit anzugeben, so meldet Ihnen der Compiler hier einen Fehler.


// 1. Basisklasse
class CBase1
{
    ....
    void DoAnything(...);
};
// 2. Basisklasse
class CBase2
{
    ....
    void DoAnything(...);
};
// Neue abgeleitete Klasse
class CMixed: public CBase1, protected CBase2
{
    void AnyMeth(...)
    {
        CBase2::DoAnything();
    }
    ....
}

Ja und das war's auch schon zu mehrfach abgeleiteten Klassen.

Beispiel

Beispiel:

Das Beispiel demonstriert das Kombinieren der Klassen string und GBase zu einer neuen Klasse ColorString, für die 'farbige' Darstellung eines Textes auf einer bestimmten Position.

Die Klasse string kennen Sie in der Zwischenzeit ja schon zu genüge. GBase soll eine Basisklasse für beliebige Grafiken sein. Der Einfachheit wegen enthält sie hier nur eine Positionsangabe. Werden beide Klassen nun zur Klasse ColorString zusammengefügt, so erhält diese neue Klasse alle Eigenschaften der Basisklassen. Zusätzlich besitzt ColorString noch die Eigenschaft color für die Verwaltung der Farbinformation.

Sehen Sie sich auch einmal an, wie in der Memberfunktion ChangeText(...) der String verändert wird. Da ColorString ja selbst kein string-Objekt enthält sondern dieses 'nur' von string erbt, können Sie natürlich auch keine Zuweisung durchführen. Glücklicherweise enthält die string-Klasse eine Memberfunktion assign(...) um den Text im String zu verändern.

Ferner sollten Sie sich noch den überladenen Operator << für die Ausgabe anschauen. Auch dort wird der im String abgelegt Text benötigt. Da die Klasse string nun Basisklasse von ColorString ist, kann die Memberfunktion c_str(...) der string-Klasse auch über ein ColorString Objekt aufgerufen werden. Das Gleiche gilt übrigens auch für die Memberfunktion GetPos(...) der GBase Klasse.

Text    : ColorString
Position: (10,20)
Farbe   : 0xcc00cc

Text    : ColorString modifiziert
Position: (200,200)
Farbe   : 0xcc00cc


// Beispiel zu mehrfach abgeleiteten Klassen

// Zuerst Dateien einbinden
#include <iostream>
#include <iomanip>
#include <string>

using std::cout;
using std::endl;
using std::string;

// Basisklasse für die Aufnahme der Grafikposition
class GBase
{
    int xPos, yPos;       // Grafikposition
public:
    GBase(int x, int y): xPos(x), yPos(y)
    {}
    // Position umsetzen
    void SetPos(int x, int y)
    {
        xPos = x; yPos = y;
    }
    // Position zurückgeben
    // Ist const-Memberfunktion!
    void GetPos(int& x, int& y) const
    {
        x = xPos; y = yPos;
    }
};

// Zusammengesetzte Klasse für die Darstellung eines
// Textes auf einer bestimmten Position in einer bestimmten Farbe
// Der Text wird in einer string-Klasse abgelegt und die
// Position in der GBase-Klasse
// Hinweis: Eigentlich sollte die Klasse string als eingeschlossenene
// Klasse definiert werden da hier eine has-a Beziehung besteht.
class ColorString: public string, public GBase
{
    unsigned long color;      // zusätzliche Farb-Info
public:
    // Konstruktor
    ColorString(int x, int y, const char* const pT, unsigned long col):
      string(pT), GBase(x, y), color(col)
    {}
    // Verschiebt Text
    void MoveIt(int x, int y)
    {
        SetPos(x, y);         // GBase-Memberfunktion hierfür aufrufen
    }
    // Ändert den Text
    void ChangeText(const char* const pT)
    {
        assign(pT);           // Memberfunktion assign() der string-Klasse
    }
    // Ausgabeoperator überladen
    friend std::ostream& operator << (std::ostream& os, const ColorString& colString);
};
// Überladene Ausgabeoperator um ein ColorString auszugeben
std::ostream& operator << (std::ostream& os, const ColorString& colString)
{
    // colString.c_str() holt const char* auf den Text im string
    os << "Text    : " << colString.c_str() << '\n';
    // Position holen von GBase
    int x, y;
    colString.GetPos(x, y);
    os << "Position: (" << x << ',' << y << ")\n";
    // Farb-Info ist eigene Eigenschaft
    os << "Farbe   : 0x" << std::hex << colString.color << std::dec << endl;
    return os;
}

// main() Funktion
int main()
{
    // ColorString Objekt definieren
    ColorString myString(10,20,"ColorString",0xcc00cc);
    // und ausgeben
    cout << myString << endl;

    // ColorString mit neuem Text und neuer
    // Position versehen
    myString.ChangeText("ColorString modifiziert");
    myString.MoveIt(200,200);
    // und erneut ausgeben
    cout << myString << endl;
}

Eine Übung entfällt an dieser Stelle!