Vererbung

Der Gedanke der Vererbung war mit der Erfindung von C++ nicht neu, aber C++ war die erste breit verwendete Sprache, die die Idee der Vererbung damit einem breiterem Publikum anbot, nämlich den reichlich vorhandenen C-Programmierern. Vererbung bedeutet nicht, dass gelöschte Objekte Informationen an nachfolgende Objekte weitergeben. Es dreht sich alles um die Datentypen.

Das Problem

Das Problem, das Vererbung zu lösen versucht, ist, dass man häufig mit ähnlichen, aber nicht identischen Daten zu tun hat. Nehmen wir folgende Klassen:

class Hund
{
public:
  char Name[64];
  int  Fellfarbe;
  int  KrallenLaenge;
  int  GefangeneKatzen;
  int  AnzahlBeine;
 
  inline void GibLaut( void ) { printf( "Wau!\n" ); }
  inline void GibNamenAus( void ) { printf( "Ich heiße %s\n", Name );
};
 
class Katze
{
public:
  char  Name[64];
  int   Fellfarbe;
  int   KrallenLaenge;
  int   GefangeneVoegel;
  int   AnzahlBeine;
 
  inline void GibLaut( void ) { printf( "Miau!\n" ); }
  inline void GibNamenAus( void ) { printf( "Ich heiße %s\n", Name ) };
};
 
class Amsel
{
public:
  char Name[64];
  int  Fellfarbe;
  int  KrallenLaenge;
  int  GefangeneInsekten;
  int  AnzahlBeine;
 
  inline void GibLaut( void ) { printf( "Piep!\n" ); }
  inline void GibNamenAus( void ) { printf( "Ich heiße %s\n", Name ); }
};

Zusammenfassen von Gemeinsamkeiten

Die Datentypen weisen sehr viele Ähnlichkeiten zueinander auf, aber sie sind nicht Identisch. Für C++ besitzen sie keinerlei Gemeinsamkeiten. Wir sehen die Funktion GibNamenAus() und sehen, dass sie insgesamt dreimal identisch existiert. Der Code ist redundant (er wiederholt sich im Quelltext). Das Ziel der Vererbung ist nun, die Gemeinsamkeiten unter einem eigenen Namen zusammen zu führen. Diese Oberklassen finden sich auch häufig bereits in der menschlichen Sprache: In unserem Fall wäre Tier eine Oberklasse von Hund, Katze und Amsel.

Wir definieren also zunächst eine Klasse Tier, die alle Gemeinsamkeiten zusammenfasst:

class Tier
{
public:
  char Name[64];
  int  Fellfarbe;
  int  KrallenLaenge;
  int  AnzahlBeine;
 
  inline void GibNamenAus( void ) { printf( "Ich heiße %s\n", Name ); }
};

Dies ist eine allgemeine Klasse, die alles beschreibt, was Tiere gemeinsam haben. Alle Tiere haben einen Namen und entsprechend gilt die Funktion GibNamenAus für alle Tiere.

Spezialisierungen

Nun gibt es aber Spezialisierungen, eben dass Hunde Katzen fange, Katzen Amseln fangen und Amseln Insekten fangen. Da Hund, Katze und Amsel alles Tiere sind, können wir die Eigenschaften des Tieres erben. Das bedeutet, dass unsere spezialisierte Klasse alles das kann, was ein Tier auch kann. Die wichtigste Form der Ableitung ist die öffentliche Ableitung, die wir hier jetzt einmal an den drei Klassen aufzeigen:

class Hund : public Tier
{
public:
  int  GefangeneKatzen;
 
  inline void GibLaut( void ) { printf( "Wau!\n" ); }
};
 
class Katze : public Tier
{
public:
  int   GefangeneVoegel;
 
  inline void GibLaut( void ) { printf( "Miau!\n" ); }
};
 
class Amsel : public Tier
{
public:
  int  GefangeneInsekten;
 
  inline void GibLaut( void ) { printf( "Piep!\n" ); }
};

Sämtlicher redundanter Code ist damit verschwunden. Wir haben ein Tier mit allen Gemeinsamkeiten beschrieben und Spezialisierungen, die zusätzliche Eigenschaften von speziellen Tieren beschreiben. Man sagt, dass die Klasse Hund die Eigenschaften von der Klasse Tier erbt.

Obwohl die Klasse Hund die Eigenschaft AnzahlBeine nicht explizit aufgeführt hat, ist sie von der Klasse Tier geerbt worden, so dass man darauf zugreifen kann.

void function( void )
{
  Hund meinHund;

  meinHund.AnzahlBeine = 4;
}

Die Ist-Ein- und Hat-Ein-Regeln

In C++ gilt die sogenannte Ist-Ein-Regel. Das bedeutet, dass Ableitungen alle Eigenschaften besitzen müssen, die die Basisklasse auch besitzt. Dies lässt sich zum Beispiel mit 'Ein Hund ist ein Tier.' ausdrücken.

Nun kann man auf die Idee kommen, ein Auto von einer Farbe abzuleiten, denn ein Auto besitzt auch eine Farbe. Aber ein Auto ist keine Farbe. Hier gilt die Hat-Ein-Regel.

Wenn eine Klasse eine Eigenschaft hat, dann ist diese Eigenschaft als Membervariable zu verwenden. Beispiel: Ein Fahrzeug hat eine Farbe. Ein Auto ist ein Fahrzeug und hat ein Radio. Die Klasse Fahrzeug hat eine Membervariable 'Farbe'. Die Klasse Auto ist von Fahrzeug abgeleitet und hat eine Membervariable 'Radio'.

Verallgemeinerung

Funktionen, die sich gar nicht so genau dafür interessieren, um welches Tier es sich genau handelt, können nun mit dem allgemeineren Datentyp geschrieben werden:

void GibAnzahlBeineAus( Tier & tier )
{
  printf( "Das Tier hat %d Beine\n", tier.AnzahlBeine );
}
 
int main( void )
{
  Hund hund;
  hund.AnzahlBeine = 4;
 
  Amsel amsel;
  amsel.AnzahlBeine = 2;
 
  GibAnzahlBeineAus( hund );
  GibAnzahlBeineAus( amsel );
 
  return EXIT_SUCCESS;  
}

Da Hund, Katze und Amseln Tiere sind, kann man sie auf den Datentyp Tier konvertieren:

Hund * hund = new Hund();
Tier * tier = hund;

oder auch

Tier * tier = new Hund();

Aber vorsicht: Der Umkehrschluss gilt nicht, denn auch wenn ein Tier eine Katze sein kann, so kann das nicht garantiert werden, sonst hätte der Entwickler ja auch den spezielleren Datentyp Katze verwendet:

Hund * hund = new Hund();
Tier * tier = hund;
Katze * katze = tier;    // Dies kann nicht kompiliert werden, da nicht garantiert werden kann, dass das Tier eine Katze ist.