C++ Kurs

Abgeleitete Klassen

Die Themen:

Mit Hilfe der Ableitung (Vererbung, Inheritance) von Klassen lassen sich prinzipiell folgende Aufgaben durchführen:

  1. Eigenschaften und Memberfunktionen, die in mehreren Klassen benötigt werden, können in eine eigene Klasse ausgelagert werden. Der Vorteil besteht darin, dass der Code für die Klasse mit den gemeinsamen Eigenschaften und Memberfunktion nur einmal geschrieben und getestet(!) werden muss.
  2. Dem Anwender einer Klasse bietet die Ableitung die Möglichkeit, eine vorgegebene Klasse um Eigenschaften und Memberfunktionen erweitern zu können, ohne dass dazu den Quellcode der Ausgangsklasse modifiziert werden muss.

Beispiel zum ersten Punkt:

Eine Bibliothek zur Darstellung von grafischen Elementen enthält eine allgemeine Klasse mit den für jedes Grafikelement erforderlichen Eigenschaften, wie z.B. Position und Farbwert, und Memberfunktionen, um diese Eigenschaften zu bearbeiten. Alle Grafikelemente verwenden nun diese allgemeine Klasse und fügen zu diesen Member ihre spezifischen Member, wie z.B. den Radius für einen Kreis, hinzu.

Beispiel zum zweiten Punkt:

Setzt ein Anwender diese Grafikbibliothek jetzt ein, so stehen ihm die vom Entwickler der Bibliothek implementierten Grafikelemente zur Verfügung. Nehmen wir einmal an, dass diese Bibliothek eine Klasse zur Darstellung eines einfarbig ausgefüllten Rechtecks enthält. Der Anwender benötigt aber ein Rechteck, das mit einem Muster ausgefüllt werden kann. Der Anwender kann jetzt die vorgegebene Klasse für das einfarbig ausgefüllte Rechteck als Basis verwenden und sie um die Eigenschaft eines Musters erweitern. Die restlichen Eigenschaften und Memberfunktionen wie z.B. die Position, kann er unverändert übernehmen. Und für diese Erweiterung benötigt der Anwender nicht einmal den Quellcode der Ausgangsklasse!

HinweisWeiter vorne haben Sie ebenfalls eine Möglichkeit kennengelernt, eine Klasse in ihrer Funktionalität zu erweitern, nämlich durch Einschließen von Objekten in eine Klasse. Und damit stellt sich nun die Frage, wann ein Objekt einer Klasse in eine andere Klasse eingeschlossen wird und wann eine Klasse abgeleitet wird. Als Faustregel können Sie sich folgenden Satz merken: ein Objekt einer Klasse wird in eine andere Klasse eingeschlossen, wenn zwischen ihnen eine hat-eine (has-a) Beziehung besteht und abgeleitet wird, wenn eine ist-eine (is-a) Beziehung besteht.

Beispiel: Gegeben seien eine Klasse Color mit Farbinformationen und eine Klasse Win zur Darstellung eines Fensters. Da ein Fenster eine Farbe hat (und keine Farbe ist), wird die Klasse Color in Win eingeschlossen. Nun soll für eine abweichende Fensterdarstellung eine neue Fensterklasse SpecWin geschrieben werden. In diesem Fall wird SpecWin von Win abgeleitet, da SpecWin ein spezielles Fenster ist (und kein Fenster 'hat'). Eigentlich nicht schwierig, oder?

Beispiel einer Ableitung

Sehen wir uns zunächst anhand eines Beispiels das prinzipielle Auslagern von gemeinsamen Member (Eigenschaften/Memberfunktionen) in eine eigene Klasse an.

Ausgangsbeispiel:

Nachfolgend sind zwei Klassen für die Darstellung von einfachen Grafikelementen definiert. Die Klasse Frame soll zur Darstellung eines Rahmens dienen und die Klasse Bar zur Darstellung eines ausgefüllten Rechtecks. Beide Klassen besitzen nun gewisse Gemeinsamkeiten, die rot hervorgehoben sind. So haben beide Klassen die Eigenschaft, dass Sie eine Position und eine Größe besitzen. Außerdem stehen in beiden Klassen Memberfunktionen zur Verfügung, um diese Eigenschaften verändern zu können.

PgmHeaderclass Frame
{
    short xPos, yPos;
    short width, height;

    ....
  public:
    Frame();
    void Draw(...);
    void SetPosition(...);
    void SetSize(...);

};
class Bar
{
    short xPos, yPos;
    short width, height;

    short fillColor;
    ....
  public:
    Bar(...);
    void Draw();
    void SetPosition(...);
    void SetSize(...);

};

Warum die Memberfunktion Draw(...) hier nicht zu den Gemeinsamkeiten zählt, das erfahren Sie gleich noch.

Basisklasse definieren

Laut vorheriger Aussage lassen sich nun diese gemeinsamen Eigenschaften und Memberfunktionen in eine eigene Klasse auslagern. Im nachfolgenden Beispiel wurden diese Gemeinsamkeiten in die Klasse GBase ausgelagert. Um diese ausgelagerten Eigenschaften und Memberfunktionen wieder zu den Klassen Frame und Bar hinzuzufügen, werden die beiden Klassen Frame und Bar nachher von der Klasse GBase abgeleitet.

PgmHeaderclass GBase
{
    short xPos, yPos;
    short width, height;
  public:
    void SetPosition(...);
    void SetSize(...);
};

class Frame   // Ableitung von GBase einfügen
{
    ....
  public:
    Frame();
    void Draw(...);

};

class Bar    // Ableitung von GBase einfügen
{
    short fillColor;
    ....
  public:
    Bar(...);
    void Draw();
};

Die jetzt noch in beiden Klassen vorhandene Memberfunktion Draw() eignet sich nicht zum Auslagern in die Klasse GBase, da sie die Objekte zeichnen soll. Und ein Rahmen wird sicherlich anders gezeichnet wie ein ausgefülltes Rechteck.

Erweitern von bestehenden Klassen

Sehen wir uns die Ableitung nun aus Anwendersicht an, d.h. dem Erweitern einer vorgegebenen Klasse. Angenommen wir haben die vorherige Klassenbibliothek mit den Grafikelementen käuflich erworben. Diese Bibliothek enthält u.a. die Klasse Bar für ein einfarbig ausgefülltes Rechteck. Wir benötigen aber ein Rechteck, das mit einem Muster (Pattern) ausgefüllt werden kann. In diesem Fall leiten wir eine neue Klasse PBar von der vorgegebenen Klasse Bar ab und fügen ihr dann die Eigenschaften des Musters sowie die entsprechenden Memberfunktionen hinzu und schon haben wir eine neue Klasse für ein Rechteck, das mit einem Muster ausgefüllt werden kann.

PgmHeaderclass PBar   // Ableitung von CBar einfügen
{
    ....
    short pattern;
  public:
    PBar(...);
    void Draw();
    void SetPattern(...);
};

Im Folgenden tauchen im Zusammenhang mit Ableitungen zwei neue Begriffe immer wieder auf:

  1. Basisklasse oder Superklasse
  2. Abgeleitete Klasse oder Subklasse

Die Basisklasse ist diejenige Klasse, die ihre Eigenschaften und Memberfunktionen an die abgeleitete Klasse weitergibt (vererbt). Die abgeleitete Klasse erbt (prinzipiell) alle Eigenschaften und Memberfunktion der Basisklasse, d.h. die abgeleitete Klasse verhält sich so, als wären die geerbten Eigenschaften und Memberfunktionen innerhalb der abgeleiteten Klasse selbst enthalten.

Ableitungen

Wie im Bild ersichtlich, kann eine Klasse sowohl Basisklasse als auch abgeleitete Klasse gleichzeitig sein. Im Beispiel ist die Klasse CBar von CGBase abgeleitete und gleichzeitig Basisklasse für die Klasse CPBar.

Zugriffsrechte

Beim Ableiten spielen auch die Zugriffsrechte public und private eine wichtige Rolle. Eine abgeleitete Klasse hat nur Zugriff auf die public (und die gleich noch aufgeführten protected) Member der Basisklasse, nicht aber auf ihre private Member. Im nachfolgenden Beispiel kann die Klasse Frame z.B. nur auf die Memberfunktionen SetPosition(...) und SetSize(...) der Basisklasse zugreifen, aber nicht auf deren Eigenschaften, da diese private sind.

PgmHeaderclass GBase
{
    short xPos, yPos;
    short width, height;
  public:
    void SetPosition(...);
    void SetSize(...);
};

class Frame  // Ableitung von GBase einfügen
{
    ....
  public:
    Frame(...);
    void Draw()
};

Eine abgeleitete Klasse erbt aber nicht nur die Eigenschaften und 'normalen' Memberfunktionen ihrer Basisklasse, sondern auch deren überladene Operatoren, mit einer einzigen Ausnahme die Sie nie vergessen sollten:

AchtungÜberlädt eine Basisklasse den Zuweisungsoperator '=', so wird dieser überladene Operator niemals an die abgeleitete Klasse vererbt!

Alle anderen, nicht-private überladenen Operatoren der Basisklasse stehen in der abgeleiteten Klasse ebenfalls zur Verfügung.

Resultierende abgeleitete Klasse

Wie stellt sich nun eine von einer Basisklasse abgeleitete Klasse (Subklasse) prinzipiell nach außen hin, d.h. für den Anwender, dar? Da die Subklasse alle Member ihrer Basisklasse erbt, sieht es für den Anwender (und auch für weitere von der Subklasse abgeleitete Klassen) so aus, als ob die nicht-private Member der Basisklasse innerhalb der Subklasse deklariert/definiert wären. private Member der Basisklasse sind niemals nach außen hin sichtbar. Dieses Verhalten ist unten durch die fiktive Klasse FFrame einmal dargestellt.

PgmHeaderclass GBase
{
    short xPos, yPos;
    short width, height;
  public:
    void SetPosition(...);
    void SetSize(...);
};

class Frame  // Ableitung von GBase einfügen
{
    ....
  public:
    Frame(...);
    void Draw()
};

Die aus der Ableitung resultierende fiktive Klasse FFrame (Anwendersicht)

class FFrame
{
    ....   // Eigenschaften von Frame
  public:
    FFrame(...);
    void Draw();            // Frame Member
    void SetPosition(...);  // geerbtes GBase Member
    void SetSize(...);      // geerbtes GBase Member
};

Zugriffsrecht protected

Bevor gleich näher auf das eigentliche Ableiten eingegangen wird, sehen wir an dieser Stelle das bis jetzt noch fehlende Zugriffsrecht protected an. Das Zugriffsrecht protected ist eine Mischung aus den beiden bereits bekannten Zugriffsrechten public und private und kann überall dort stehen, wo Zugriffsrechte erlaubt sind. Auf die protected Member einer Klasse kann nur von abgeleiteten Klassen aus direkt zugegriffen werden, d.h. sie verhalten aus der Sicht der abgeleiteten Klasse aus wie public Member. Der Zugriff auf protected Member aus anderen Klassen oder Funktionen heraus ist jedoch nicht möglich. Hier verhalten sich die protected Member also wie private Member. Im Beispiel können Memberfunktionen der Klasse Frame direkt auf die protected Member der Klasse GBase zugreifen während alle anderen Klassen oder Funktionen keinen Zugriff darauf haben.

PgmHeaderclass GBase
{
  protected:
    short xPos, yPos;
    short width, height;
    ....
};
// Definition der abgeleiteten Klasse
class Frame // Ableitung GBase einfügen
{
    ....
};

Ableitung

Doch sehen wir uns jetzt endlich an, wie eine Klasse von einer anderen Klasse abgeleitet wird.

Das Ableiten eine Subklasse von einer Basisklasse erfolgt nach folgender Syntax:

class CSub: ACCESS CSuper
{
    ....
};

CSub ist der Name der abgeleiteten Klasse und CSuper der Name der Basisklasse. ACCESS gibt an, mit welchem Zugriffrecht aus der Sicht des Anwenders oder weiterer abgeleiteten Klassen die Member der Basisklasse in die abgeleitete Klasse übernommen werden. Für ACCESS können folgende Schlüsselwörter stehen:

public, protected, private

Wie gesagt, das Zugriffsrecht ACCESS spielt nur aus der Sicht des Anwenders oder weiteren abgeleiteten Klassen eine Rolle. Die abgeleitete Klasse selbst hat immer Zugriff auf alle public und protected Elemente ihrer Basisklasse.

Beispiel für eine Ableitung:

PgmHeader// Definition der Basisklasse
class GBase
{
    ....
};
// Definition der abgeleiteten Klasse
class Frame: public GBase
{
    ....
};

Wie sich diese Zugriffsrechte im Einzelnen auswirken soll anhand eines Beispiels demonstriert werden.

Die public Ableitung

Vorgegeben sind die drei Klassen GBase, Frame und MyFrame. GBase enthält die drei Member pub, prot und priv mit den entsprechenden Zugriffsrechten. Frame ist nun public von GBase abgeleitet. Bei einer public Ableitung werden die Zugriffsrechte aus der Basisklasse 1:1 in die abgeleitete Klasse übernommen. Damit kann dann auch über ein Objekt der abgeleiteten Klasse Frame auf das public Member pub der Basisklasse zugegriffen werden.

PgmHeader// Definition der Basisklasse
class GBase
{
  public:    short pub;
  protected: short prot;
  private:   short priv;
};
// Von GBase abgeleitete Klasse
class Frame: public GBase
{
    ....
};
// Weitere Klasse von Frame ableiten
class MyFrame: public Frame
{
   ....
   void AnyMethode()
   {
       pub  = ....;   // ist erlaubt
       prot = ....;   // ist erlaubt
       priv = ....;   // ist nicht erlaubt
   }
}
// Objekt der Klassen Frame definieren
Frame   frameObj;
// Zugriffe
frameObj.pub  = ...;  // ist erlaubt
frameObj.prot = ...;  // nicht erlaubt
frameObj.priv = ...;  // nicht erlaubt

Die protected Ableitung

Leiten wir jetzt die Klasse Frame protected von GBase ab. Die Ableitung der Klasse MyFrame lassen wir gegenüber vorhin unverändert auf public. Durch die protected Ableitung werden alle public-Member der Basisklasse zu protected-Member der abgeleiteten Klasse. Der Unterschied zum vorherigen Beispiel liegt nur im Zugriffsrecht auf die public Member der Basisklasse. Damit können zwar die Memberfunktionen der Klassen Frame und MyFrame immer noch auf das Member pub zugreifen, aber der direkte Zugriff darauf aus der Applikation heraus über Objekte von Typ Frame und MyFrame ist gesperrt.

Selbstverständlich kann aber über ein Objekt der Basisklasse GBase immer noch auch das public Member pub zugegriffen werden.

PgmHeader// Definition der Basisklasse
class GBase
{
  public:    short pub;
  protected: short prot;
  private:   short priv;
};
// Von GBase abgeleitete Klasse
class Frame: protected GBase
{
    ....
};
// Weitere Klasse von Frame ableiten
class MyFrame: public Frame
{
   ....
   void AnyMethode()
   {
       pub  = ....;   // ist erlaubt
       prot = ....;   // ist erlaubt
       priv = ....;   // ist nicht erlaubt
   }
}
// Objekt der Klassen Frame definieren
Frame   frameObj;
// Zugriffe
frameObj.pub  = ...;  // nicht erlaubt
frameObj.prot = ...;  // nicht erlaubt
frameObj.priv = ...;  // nicht erlaubt

Die private Ableitung

Und nun zum letzten Fall, der private Ableitung. Jetzt werden alle vererbten Member der Basisklasse zur private Member der abgeleiteten Klasse. Damit besitzt nur noch die Klasse Frame Zugriff auf die public und protected Member der Basisklasse GBase. Selbst die Memberfunktionen der von Frame abgeleiteten Klasse MyFrame haben jetzt keinen Zugriff mehr auf die Member der Klasse GBase. Dies ist die restriktivste Form der Ableitung. Objekte vom Typ Frame haben auch hier ebenfalls keinen Zugriff auf die Member der Basisklasse.

PgmHeader// Definition der Basisklasse
class GBase
{
  public:    short pub;
  protected: short prot;
  private:   short priv;
};
// Von GBase abgeleitete Klasse
class Frame: private GBase
{
    ....
};
// Weitere Klasse von Frame ableiten
class MyFrame: public Frame
{
   ....
   void AnyMethode()
   {
       pub  = ....;   // ist nicht erlaubt
       prot = ....;   // ist nicht erlaubt
       priv = ....;   // ist nicht erlaubt
   }
}
// Objekt der Klassen Frame definieren
Frame   frameObj;
// Zugriffe
frameObj.pub  = ...;  // nicht erlaubt
frameObj.prot = ...;  // nicht erlaubt
frameObj.priv = ...;  // nicht erlaubt

Zusammenfassung

Die nachfolgende Tabelle zeigt nochmals in einer Übersicht, wie die Zugriffsrechte von Basisklassen-Member beim Ableiten umgewandelt werden:

Access Member Superklasse Member Subklasse
privat private kein Zugriff
  protected  private Member
  public private Member
protected private kein Zugriff
  protected protected Member
  public protected Member
public private kein Zugriff
  protected protected Member
  public public Member

Konstruktor und Destruktor

Damit haben wir das Prinzip des Ableitens abgehandelt und wir können zum nächsten Schritt übergehen, der Abarbeitung der Konstruktore und Destruktore beim Ableiten von Klassen.

Als Ausgangspunkt dient wieder die Klasse GBase sowie die von ihr abgeleitete Klasse Frame. Beide Klassen enthalten nun entsprechende Konstruktore. Bei der Definition eines Objekts der Klasse Frame ist die Position und Größe des Rahmens sowie die Linienfarbe des Rahmens anzugeben. Da Frame selbst aber nur die Eigenschaft lineColor enthält, müssen die anderen Daten irgendwie an die Basisklasse GBase weitergegeben werden. GBase wiederum enthält einen entsprechenden Konstruktor, der die erforderlichen Daten als Parameter erhält. Wir müssen jetzt also beim Definieren eines Frame Objekts nur noch irgendwie den Konstruktor von GBase aufrufen.

PgmHeaderclass GBase
{
    ....
  public:
     GBase (short x, short y, short w, short h);
    ....
};

class Frame: public GBase
{
    ....
    short lineColor;
  public:
    Frame(short x, short y, short w, short h, short lCol);
};

// Definition eines Frame Objekts
Frame   myFrame(10,20,640,480,0xC0C0CO);

Konstruktor der abgeleiteten Klasse

Der Aufruf des Konstruktors der Basisklasse erfolgt bei der Definition des Konstruktors der abgeleiteten Klasse mit folgender Syntax:

CSub::CSub(P1, P2,...): CSuper(Pa, Pb,...)
{
    ....
}

CSub ist der Name der abgeleiteten Klasse und CSuper der Name der Basisklasse. D.h. nach der Parameterklammer des Konstruktors der abgeleiteten Klasse folgt zunächst ein Doppelpunkt und danach der Aufruf des Konstruktors der Basisklasse. An diesen Konstruktor werden dann die erforderlichen Daten als Parameter übergeben, wobei es keine Rolle spielt, ob die zu übergebenden Daten Parameter des Konstruktors der abgeleiteten Klasse sind (P1, P2,...) oder anderweitig bestimmt werden, zum Beispiel als Konstanten. Aber dieses Vorgehen kennen Sie ja prinzipiell schon vom delegating ctor her.

Damit kann der Konstruktor der Klasse Frame wie unten angegeben definiert werden. Wie zu ersehen ist, werden die Parameter x, y, w und h einfach an den Konstruktor der Basisklasse GBase weitergegeben. Der Konstruktor von Frame merkt sich nur die Eigenschaften seiner Klasse, nämlich die Linienfarbe.

PgmHeaderclass GBase
{
    ....
  public:
     GBase (short x, short y, short w, short h);
    ....
};

class Frame: public GBase
{
    ....
    short lineColor;
  public:
    Frame(short x, short y, short w, short h, short lCol);
};

// Definition des Konstruktors
Frame::Frame (short x, short y, short w, short h, short lCol):
       GBase(x, y, w, h)
{
    lineColor = lCol;
}
HinweisBeachten Sie, dass der Aufruf des Konstruktors der Basisklasse nur bei der Definition des Konstruktors der abgeleiteten Klasse steht. An der Deklaration des Konstruktors ändert sich nichts. Und übersehen nicht den unscheinbaren Doppelpunkt nach der Parameterklammer des Konstruktors der abgeleiteten Klasse!

Konstruktor bei mehrstufigen Ableitungen

Doch wie sehen im Fall einer mehrstufigen Ableitung (GBase -> Frame -> MyFrame) die Definitionen der Konstruktore aus? Muss der Konstruktor vom MyFrame sowohl den Konstruktor von Frame als auch den Konstruktor von GBase aufrufen?

Nein, so schlimm wird's dann doch nicht! Dies würde sonst sehr schnell zu recht unübersichtlichen Konstruktoren in abgeleiteten Klassen führen. Der Konstruktor von MyFrame muss (und darf) nur den Konstruktor seiner Basisklasse aufrufen, also den von Frame. Und der Konstruktor von Frame ruft dann wiederum den seiner Basisklasse, also GBase, auf. Im Beispiel sehen Sie die prinzipiellen Definitionen aller drei Konstruktore. Beim Aufruf des Konstruktors der Klasse Frame wird im Beispiel für den letzten Parameter zur Demonstration eine Konstante übergeben.

PgmHeader// Konstruktor der Klasse GBase
GBase::GBase(short x, short y, short w, short h)
{
    ....
};
// Konstruktor der Klasse Frame
Frame::Frame(short x, short y, short w, short h, short lCol):
      GBase(x, y, w, h)
{
    ....
}
// Konstruktor der Klasse MyFrame
MyFrame::MyFrame(short x, short y, short w, short h, short any):
     Frame(x, y, w, h, 0xFF)
{
    ....
}

Dieser Aufrufmechanismus funktioniert sogar auch dann, wenn eine Basisklasse selbst keinen Konstruktor oder nur den Standard-Konstruktor besitzt. In diesem Fall braucht der Konstruktor der abgeleiteten Klasse gar nichts tun. Es wird immer automatisch der 'nächst höhere' Konstruktor aufgerufen.

Aufruf-Reihenfolge der Konstruktore

Wird ein Objekt einer abgeleiteten Klasse definiert, so läuft folgender Vorgang ab. Zuerst wird immer der Konstruktor der Basisklasse ausgeführt, damit sichergestellt ist, dass beim Eintritt in den Konstruktor der abgeleiteten Klasse alle Eigenschaften der Basisklasse bereits initialisiert sind.

Ist die Basisklasse selbst wiederum von einer anderen Klasse abgeleitet, so wird zuerst der Konstruktor deren Basisklasse ausgeführt. Im Beispiel werden die Klassen GBase, Frame und MyFrame mit den bereits bekannten Ableitungen definiert. Da GBase die 'oberste' Basisklasse ist, wird auch deren Konstruktor als erstes ausgeführt. Anschließend wird der Konstruktor von Frame ausgeführt und ganz zum Schluss der Konstruktor der 'untersten' Klasse MyFrame.

PgmHeaderclass GBase
{
    ....
};

class Frame: public GBase
{
    ....
};

class MyFrame: public Frame
{
    ....
};

Aufruf-Reihenfolge der Konstruktoren:

  1. Konstruktor von GBase
  2. Konstruktor von Frame
  3. Konstruktor von MyFrame

Aufruf-Reihenfolge der Destruktoren

Und was für die Konstruktore gilt, gilt auch für die Destruktoren. Nur ist hier die ganze Sache etwas einfacher, da ein Destruktor keine Parameter besitzt und auch (fast) niemals direkt aufgerufen wird. Beim Schreiben eines Destruktors müssen Sie also keine Rücksicht darauf nehmen, ob die Klasse von einer anderen Klasse abgeleitet ist oder nicht. Wird ein Objekt, das in der Ableitungshierarchie ganz unten steht, entfernt, so wird zuerst dessen Destruktor ausgeführt. Anschließend wird der Destruktor seiner Basisklasse ausgeführt und dann der Destruktor der nächst höheren Basisklasse. Die Aufruf-Reihenfolge ist hier also genau umgekehrt wie bei den Konstruktoren.

PgmHeaderclass GBase
{
    ....
};

class Frame: public GBase
{
    ....
};

class MyFrame: public Frame
{
    ....
};

Aufruf-Reihenfolge der Destruktoren:

  1. Destruktor von MyFrame
  2. Destruktor von Frame
  3. Destruktor von GBase

Zugriff auf Basisklassen-Member

Doch nun genug abgeleitet, sehen wir uns jetzt an, wie auf Member innerhalb von abgeleiteten Klassen zugegriffen wird.

Beginnen wir mit dem Zugriff auf die Member einer Basisklasse aus den Memberfunktionen einer abgeleiteten Klasse heraus. Wie schon erwähnt, können abgeleitete Klassen auf alle nicht-private Member der Basisklasse zugreifen. Hierzu reicht es im Normalfall aus, wenn der entsprechende Membername angegeben wird. Nicht-private Member von Basisklassen verhalten sich für abgeleitete Klassen ja prinzipiell wie eigene Member. Im Beispiel wird aus der Memberfunktion DoAnything(...) der abgeleiteten Klasse auf die Eigenschaft xPos der Basisklasse zugegriffen und deren Memberfunktion SetSize(...) aufgerufen.

PgmHeaderclass GBase
{
  protected:
    short xPos, yPos;
    short width, height;
  public:
    void SetPosition(...);
    void SetSize(...);
};

class Frame: public GBase
{
    ....
  public:
    Frame(...);
    void DoAnything()
};
// Zugriff auf Member der Basisklasse
void Frame::DoAnything()
{
    xPos -= 10;       // Dies sind Member der
    SetSize(100,100); // der Basisklasse!
    ....
}

In wie weit der direkte Zugriff auf Basisklassen-Eigenschaften aber einer sauberen Programmierung entspricht, wollen wir einmal offen lassen. Das Beispiel dient nur zur Demonstration, wie auf die Member einer Basisklasse zugegriffen werden kann.

Gleiche Member in Basis- und Sub-Klasse

Dieser direkte Zugriff funktioniert aber nur so lange, wie die abgeleitete Klasse keine Member mit gleichem Namen besitzt wie die Basisklasse. Besitzt die abgeleitete Klasse Member mit gleichem Namen wie Member in der Basisklasse, so wird standardmäßig immer das Member der eigenen, hier abgeleiteten, Klasse angesprochen. Um das Member der Basisklasse anzusprechen, ist vor dem Membernamen noch der Name der Basisklasse anzugeben, gefolgt vom Zugriffsoperator ::.

PgmHeaderclass GBase
{
  protected:
    short xPos, yPos;
    short width, height;
  public:
    void SetPosition(...);
    void SetSize(...);
};
class Frame: public GBase
{
    ....
    short width;
  public:
    Frame(...);
    void DoAnything()
};
// Zugriff auf Member der Basisklasse
void Frame::DoAnything()
{
    width++;         // Frame Member
    GBase::width++;  // Basisklassen-Member
    ....
}

Zugriff auf Basisklassen-Member über Objekte

Auch beim Zugriff über Objekte einer abgeleiteten Klasse auf die Basisklassen-Member reicht im Regelfall die Angabe des Membernamens aus. Der erste Zugriff unten über ein Objekt der Klasse Frame ruft die Memberfunktion SetPosition(...) der Klasse GBase auf. Ebenfalls wird die Memberfunktion SetSize(...) der Klasse GBase aufgerufen, wenn der Aufruf über ein weiter abgeleitetes Objekt der Klasse MyFrame erfolgt. Dieser Zugriffsmechanismus funktioniert also auch über mehrere Ableitungsebenen hinweg.

PgmHeaderclass GBase
{
  protected:
    short xPos, yPos;
    short width, height;
  public:
    void SetPosition(...);
    void SetSize(...);
};
class Frame: public GBase
{
    ....
};
class MyFrame: public Frame
{
    ....
};
// Objekt der abgeleiteten Klassen
Frame   frameObj;
MyFrame myFrameObj;
// Zugriff auf public Member von GBase
frameObj.SetPosition(...);
myFrameObj.SetSize(...);

Verdeckte Basisklassen-Member

Im Zusammenhang mit Aufrufen von Basisklassen-Memberfunktionen soll noch auf eine kleine Stolperfalle hingewiesen werden. Sehen wir uns zunächst einmal die nachfolgend dargestellten Klassen an. Sowohl Frame als auch MyFrame besitzen jeweils eine Memberfunktion Move(...). Die Memberfunktion der Klasse Frame besitzt jedoch einen Parameter und die der Klasse MyFrame zwei Parameter.

PgmHeaderclass Frame: public GBase
{
  public:
    void Move(short x);
    ....
};
class MyFrame: public Frame
{
  public:
    void Move(short x, short y);
    ....
};

Aufbauend auf diesen Klassendefinitionen sehen wir uns einige Aufrufe der Memberfunktionen Move(...) an.

PgmHeader// Objekts der abgeleiteten Klassen
Frame   frameObj;
MyFrame myFrameObj;
// Aufrufe der Memberfunktion Move(...)
frameObj.Move(10);            // Move() der Klasse Frame
myFrameObj.Move(10,20);       // Move() der Klasse MyFrame
myFrameObj.Move(10);          // Fehler!
myFrameObj.Frame::Move(10);   // Move() der Klasse Frame!

Die Interpretation der ersten beiden Aufrufe im Beispiel sollten Ihnen jetzt keine mehr Schwierigkeiten bereiten. Hier wird einfach die zum Objekt gehörige Memberfunktion aufgerufen. 

Einen Fehler verursacht dagegen der dritte Aufruf. Was hier versucht wird, ist, die Memberfunktion Move(...) der Klasse Frame über ein MyFrame Objekt aufzurufen, was aber fehlschlägt. Eine Memberfunktion der abgeleiteten Klasse verdeckt standardmäßig gleichnamige Memberfunktionen und Eigenschaften ihrer Basisklasse(n). Um trotzdem auf die Memberfunktion der Move(...) der Klasse Frame über ein MyFrame Objekt zuzugreifen, muss, wie im vierten Aufruf angegeben, nach dem Punktoperator zunächst den Klassennamen, dann den Zugriffsoperator :: und danach die Memberfunktion angeben.

Basisklassenzeiger

So, jetzt wissen wir schon fast alles über einfache Ableitungen. Im Zusammenhang mit abgeleiteten Klassen sei an dieser Stelle vorab noch auf folgenden wichtigen Sachverhalt hingewiesen:

HinweisIn einem Zeiger vom Typ der Basisklasse kann auch ein Zeiger vom Typ einer abgeleiteten Klasse abgelegt werden.

Diese Eigenschaft ist unten dargestellt. Sie spielt später im Kapitel über virtuelle Memberfunktionen noch eine entscheidende Rolle. Die selbstverständliche Entfernung der erzeugten Objekte ist im Beispiel nicht explizit aufgeführt!

PgmHeaderclass GBase
{
    ....
};
class Frame: public GBase
{
    ....
};
class MyFrame: public Frame
{
    ....
}
int main()
{
    GBase *pBase;
    pBase = new Frame(...);     // Adresse Frame Objekt zuweisen
    ....
    pBase = new MyFrame(...);   // Adresse MyFrame Objekt zuweisen!
}

Einblenden von Basisklassen-Memberfunktionen

Memberfunktionen einer abgeleiteten Klasse verdecken die gleichnamigen Memberfunktionen ihrer Basisklasse. Nun kann es aber durchaus sinnvoll sein, das Memberfunktionen der Basisklasse auch für Objekte der abgeleiteten Klasse verwendet werden können. Um dieses zu erreichen, musste bisher innerhalb der abgeleiteten Klasse eine entsprechende Memberfunktion definiert werden, welche dann die Memberfunktion der Basisklasse aufruft. Doch es geht auch einfacher. Hierzu ist in der abgeleiteten Klasse explizit die Schnittstelle der Basisklasse mittels einer using-Anweisung freizugeben. Die allgemeine Syntax dieser using-Anweisung ist wie folgt:

using BASISKLASSE::MEMBER;

Sehen wir uns dazu das nachfolgende Beispiel an.

PgmHeader// Basisklasse
class Frame
{
  public:
    void Move(short x);         // Move() mit einem Parameter
    ...
};
// Abgeleitete Klasse
class MyFrame: public Frame
{
  public:
    using Frame::Move;            // Blendet Move() der Basisklasse ein
    void Move(short x, short y);  // Move() mit zwei Parameter
    ...
};

int main()
{
    Frame frObj;                 // Objekt der Basisklasse
    MyFrame mfrObj;              // Objekt der abgeleiteten Klasse

    frObj.Move(10);              // Ruft Move(short) der Basisklasse auf
    mfrObj.Move(10,30);          // Ruft Move(short,short) der abgel. Klasse auf
    mfrObj.Move(20);             // Ruft Move(short) der Basisklasse auf!
}

Sowohl die Basisklasse Frame als auch die davon abgeleitete Klasse MyFrame besitzen wieder eine Memberfunktion Move(...). Die Basisklassen Memberfunktion erwartet einen Parameter beim Aufruf und die der abgeleiteten Klasse zwei Parameter. Um nun die Move(...) Memberfunktion der Basisklasse auch über ein Objekt der abgeleiteten Klasse aufrufen zu können, wird innerhalb der abgeleiteten Klasse mit Hilfe der using-Anweisung diese Memberfunktion eingeblendet.

Hierbei ist zu beachten, dass zum einen die using-Anweisung innerhalb der public-Sektion der abgeleiteten Klasse steht. Würde die using-Anweisung nicht in der public-Sektion stehen, würde der letzte Aufruf im obigen Beispiel zu einer Fehlermeldung des Compilers führen. Und zum anderen wird auch nur der Name der Memberfunktion angegeben, also ohne deren Parameter. Daraus folgt, enthält eine Basisklasse mehrere Memberfunktionen mit gleichem Namen, aber mit unterschiedlichen Parametern, so werden alle Memberfunktionen mit diesem Namen eingeblendet.

Damit wäre dieses doch recht umfangreiche Kapitel fast beendet. Zum Schluss sehen wir uns nochmals genauer an, wie eine vorgegebene Klasse mittels Ableitung angepasst werden kann.

Erweitern von Klassen durch Ableitung

Wenn eine Klassenbibliothek käuflich erworben wird, so wird nur im Ausnahmefall der Quellcode der Bibliothek mit ausgeliefert. In der Regel wird der übersetzte Quellcode in Form einer Objektdatei xxx.obj oder Bibliotheksdatei xxx.lib, sowie die dazugehörige Header-Datei xxx.h geliefert. Wenn nun eine Klasse aus der Klassenbibliothek eingesetzt wird, muss zum einen die Header-Datei  xxx.h in den Quellcode eingebunden und zum anderen der Objektcode xxx.obj bzw. xxx.lib zum Programm dazugelinkt werden (hier nicht dargestellt).

PgmHeader// Modul myprog.cpp welches die Klasse OClass verwendet
#include "oclass.h"       // Einbinden der Headerdatei
....
int main()
{
    OClass theObject;     // OClass Objekt definieren
    ...
}

Um jetzt eine vorgegebene Klasse anzupassen (hier OClass), geht man ist am besten wie folgt vor:

1. Es wird eine neue Header-Datei für die Klassendefinition der neuen Klasse erstellt (hier MyClass.h).

PgmHeader#ifndef MYCLASS_H
#define MYCLASS_H
....
#endif

2. Diese neue Header-Datei bindet die Header-Datei der zu erweiternden Klasse ein (hier oclass.h).

PgmHeader#ifndef MYCLASS_H
#define MYCLASS_H
#include "oclass.h"
....
#endif

3. In der Header-Datei wird die neue Klasse von der zu erweiternden Klasse abgeleitet und diese neue Klasse entsprechend den Anforderungen erweitert.

PgmHeader#ifndef MYCLASS_H
#define MYCLASS_H
#include "oclass.h"
class MyClass: public OClass
{
    ....
};
#endif

4. Anschließend wird eine Quellcode-Datei erstellt, in der die Memberfunktionen der neuen Klasse definiert werden. Sinnvollerweise erhält diese Datei den gleichen Namen wie der Header-Datei, jetzt natürlich aber mit der Erweiterung cpp.

PgmHeader#include "myclass.h"
.... // hier die Memberfunktionen der
.... // neuen Klasse definieren

5. Zum Schluss wird anstelle der ursprünglichen Header-Datei die neue Header-Datei eingebunden und die Quellcode-Datei der neuen Klasse (myclass.cpp) zum Projekt hinzugefügt (hier nicht dargestellt).

PgmHeader#include "myclass.h"
....
int main()
{
    MyClass myObj;
    ....
}

So einfach geht's, wenn man es weiß.

Beispiel und ÜbungUnd hier geht's zum Beispiel und zur Übung.