Vererbung

Ein Prinzip der Objektorientierung ist das Vererben von Attributen und Methoden einer Klasse an eine davon abgeleitete, speziellere, Klasse. Sehen wir uns das Beispiel an:

// Klasse für Obst
class CFruit
{
  public: // folgende Elemente sind öffentlich

    // Konstruktor: Standardfarbe schwarz
    CFruit()
    { m_r = m_g = m_b = 0.0f; }

    // Destruktor vituell, d.h. alles klar zum Ableiten
    virtual ~CFruit()
    { }

    // Methode verändert keine Werte, darum const
    void printColor() const
    { cout << m_r << " " << m_g << " " << m_b << endl; }

  protected: // folgende Elemente sind geschützt

    float m_r, m_g, m_b; // Farbwerte
};

Alle Methoden wurden der Lesbarkeit halber hier inline innerhalb der Klasse definiert. inline-Funktionen werden normalerweise aus Geschwindigkeitsgründen verwendet. Anstatt einen Funktionsaufruf zu tätigen, werden inline-Funktionen mittels cut-and-paste direkt in den aufrufenden Code hineinkopiert. Hier dienen sie, wie gesagt nur der Kürze des Codes.

Diese Klasse funktioniert ohne Probleme und stellt eine schwarze Frucht dar, deren Farbe ausgegeben werden kann. Sie besitzt drei geschützte Attribute für die Farben rot, grün und blau. Darüber hinaus fällt noch auf, dass die Methode printColor hinter der Deklaration ein const trägt. An dieser Stelle besagt const, dass die Methode keine Parameter der Klasse verändert. Wir werden es weiter unten darauf zurückkommen.

Apfel ist Obst

Apfel ist sicher eine Unterform von Obst mit einer speziellen Farbe, etc. Leiten wir also die Klasse der Äpfel aus der Klasse Obst ab...

// Klasse für Äpfel (alles von CFruit öffentlich erben)
class CApple : public CFruit
{
  public: // folgende Elemente sind öffentlich

    CApple()
    { m_r = 0.0f; m_g = 1.0f; m_b = 0.0f; }

    virtual ~CApple()
    { }
};

Was hier passiert ist folgendes: Die Klasse CApple erbt alle Methoden und Eigenschaften der Klasse CFruit. Das bedeutet im Grunde, dass Methoden und Attribute für CApple genau so existieren, wie für CFruit.

Es bedeutet unter anderem, dass wir auf die Farbattribute zugreifen dürfen, obwohl sie hier gar nicht auftauchen, sondern weiter oben in der Deklaration einer anderen Klasse. Die Farbattribute waren als protected deklariert. Auf alle als public und protected deklarierten Klassenelemente darf die Unterklasse unbegrenzt zugreifen. Auf Elemente der Oberklasse, die unter der Rubrik private stehen, haben nicht einmal abgeleitete Objekte Zugriff.

Konstruktor und Destruktor werden nicht eins zu eins vererbt, sondern rekursiv aufgerufen. Das bedeutet, dass bei Erzeugen einer Instanz der Apfelklasse zuerst der CFruit Standardkonstruktor aufgerufen wird und danach der CApple Konstruktor. Bei den Destruktoren (die virtuell sein müssen!) geht diese Aufrufkette rückwärts. Zunächst wird der Destruktor von CApple aufgerufen, danach der von CFruit.

Konstruktion und Destruktion

Im Beispiel gibt es die Funktion printColor, die in der Lage ist, die Farbe einer beliebigen Frucht auszugeben.

void printColor(CFruit fruit)
{
    fruit.printColor();
}

Dieser Funktion können wir nun Äpfel oder generische (schwarze) Früchte übergeben:

int main()
{
    // generische Frucht und Apfel erzeugen
    CFruit  fruit;
    CApple  apple;

    // von allen Früchten die Farbe ausgeben
    printColor(fruit);
    printColor(apple);

    return 0;

} // main()

Innerhalb der printColor-Funktion besitzen wir nur Zugriff auf die Elemente von CFruit, die allerdings in diesem sehr simplen Beispiel mit der von CApple übereinstimmen. Sinnvoller wird das Ganze, wenn wir mehrere Klassen von einer Grundklasse ableiten, wozu wir im nächsten Kapitel kommen.

Selbständige Programmierung
  • Deklariere die Farbattribute in CFruit als private: und sieh, was der Compiler dazu meint.
  • Gebe in Konstruktor und Destruktor der Klassen einen Text aus. Was passiert beim Aufruf der Funktion printColor?

zurück