Menu

C++-Vererbung: Basis- und abgeleitete Klassen erklärt

Lerne, wie die Vererbung in C++ einer abgeleiteten Klasse erlaubt, eine Basisklasse wiederzuverwenden und zu erweitern: die Syntax, öffentliche vs. private Vererbung, die Reihenfolge von Konstruktoren und Destruktoren sowie Fallstricke wie das Object Slicing.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Eine Klasse durch Erweitern wiederverwenden

Du hast Klassen mit Konstruktoren gebaut und mit Destruktoren aufgeräumt. Vererbung ist der nächste Schritt: Statt die Member einer Klasse in eine andere zu kopieren, deklarierst du, dass eine neue Klasse eine spezialisierte Version einer vorhandenen ist, und lässt sie alles automatisch erben.

Die vorhandene Klasse ist die Basisklasse (oder Elternklasse); die neue ist die abgeleitete Klasse (oder Kindklasse). Eine abgeleitete Klasse beginnt mit allen Daten und dem gesamten Verhalten der Basis und fügt dann hinzu oder ändert, was sie unterscheidet. Das ist C++ wichtigstes Werkzeug für die „ist-ein"-Beziehung: Ein Dog ist ein Animal, ein SavingsAccount ist ein BankAccount.

Grundlegende Syntax: class Derived : public Base

Du erbst, indem du dem Namen der abgeleiteten Klasse einen Doppelpunkt, einen Zugriffsspezifizierer und den Namen der Basisklasse folgen lässt. Die häufigste Form ist die public-Vererbung.

Dog deklariert weder name noch eat(), und dennoch funktionieren beide an einem Dog-Objekt, weil sie von Animal geerbt wurden. Die abgeleitete Klasse kann frei Member wie bark() hinzufügen, von denen die Basis nichts weiß.

protected: Member nur für Kinder

Ein private-Member der Basis ist innerhalb der abgeleiteten Klasse nicht zugänglich - Vererbung bricht die Kapselung nicht. Wenn die Interna der Basis vor der Außenwelt verborgen, aber für Unterklassen verfügbar sein sollen, verwende den Zugriffsspezifizierer protected.

Stell dir die drei Ebenen als konzentrische Ringe vor: private heißt „nur diese Klasse", protected heißt „diese Klasse und ihre Nachfahren" und public heißt „alle". Siehe Zugriffsspezifizierer für das vollständige Bild.

Reihenfolge von Konstruktoren und Destruktoren

Ein abgeleitetes Objekt enthält ein Basis-Teilobjekt, und dieser Basisteil muss am Leben sein, bevor der abgeleitete Teil aufgebaut wird. Deshalb läuft die Konstruktion von der Basis aus zuerst und die Destruktion vom Abgeleiteten aus zuerst (genau umgekehrt). Wenn die Basis Konstruktorargumente braucht, gibst du sie über die Member-Initialisierungsliste weiter.

Die Ausgabe macht die Reihenfolge greifbar:

Animal ctor: Rex   // die Basis wird zuerst gebaut
Dog ctor           // dann der abgeleitete Teil
Dog dtor           // in umgekehrter Reihenfolge zerstört...
Animal dtor: Rex   // ...die Basis zuletzt

Wenn du das : Animal(n) vergisst und die Basis keinen Standardkonstruktor hat, lässt sich der Code nicht kompilieren - C++ hat keine Ahnung, wie es den Basisteil bauen soll. Eine Basisklasse, von der du erben willst, sollte fast immer einen Destruktor deklarieren (und, wie die nächste Seite zeigt, oft einen virtuellen).

Überschreiben: eine Basismethode neu definieren

Eine abgeleitete Klasse kann eine geerbte Methode ersetzen, indem sie eine mit derselben Signatur deklariert. Über Base::method() erreichst du weiterhin das Original.

Das ist schlichtes Name Hiding, kein Polymorphismus: Welche describe() läuft, wird zur Kompilierzeit anhand des statischen Typs der Variablen entschieden. Das ist eine entscheidende Einschränkung - wenn du über ein Shape& oder einen Shape* aufrufst, der tatsächlich auf ein Circle zeigt, bekommst du trotzdem Shape::describe(). Das zu beheben erfordert virtual, das Thema der nächsten Seite.

Vorsicht vor Object Slicing

Da eine Basisreferenz oder ein Basiszeiger auf ein abgeleitetes Objekt verweisen kann, ist es verlockend, ein abgeleitetes Objekt in eine Variable vom Basistyp zu kopieren. Tu es nicht - der abgeleitete Teil wird abgeschnitten (sliced).

a ist ein echtes Animal, kein Dog mit einem Basis-Etikett, also existiert breed darauf schlicht nicht. Um mit abgeleiteten Objekten polymorph zu arbeiten, musst du eine Basisreferenz (Animal&) oder einen Zeiger (Animal*) verwenden, niemals einen Basiswert. Slicing geschieht still - es kompiliert sauber und verwirft einfach klammheimlich Daten -, was es zu einem der am leichtesten in Produktion gelangenden Vererbungsfehler macht.

Häufige Fehler, die du vermeiden solltest

  • Erwarten, dass private-Member der Basis im Kind erreichbar sind. Sind sie nicht. Verwende protected für Daten, die die abgeleitete Klasse berechtigterweise braucht, und halte wirklich internen Zustand private.
  • Vergessen, die Argumente des Basiskonstruktors weiterzureichen. Hat die Basis keinen Standardkonstruktor, musst du ihn explizit in der Initialisierungsliste des abgeleiteten Konstruktors aufrufen (: Base(args)).
  • Ein abgeleitetes Objekt in einen Basiswert slicen. Ein Dog in ein Animal zu kopieren verwirft alles Dog-Spezifische. Übergib und speichere stattdessen Basisreferenzen oder -zeiger.
  • Vererbung zur Codewiederverwendung überstrapazieren. Vererbung modelliert „ist-ein". Ist die Beziehung in Wahrheit „hat-ein" (ein Car hat einen Engine), bevorzuge Komposition - ein Member-Objekt - statt zu erben.

Weiter: Virtuelle Funktionen

Das gerade gesehene Überschreiben wurde zur Kompilierzeit aufgelöst, daher ignorierte ein Aufruf über einen Basiszeiger die abgeleitete Version. Die nächste Seite, virtuelle Funktionen, führt das Schlüsselwort virtual und override ein - den Mechanismus, der den Laufzeittyp entscheiden lässt, welche Methode ausgeführt wird; er schaltet echten Polymorphismus frei und erklärt, warum Basisklassen virtuelle Destruktoren brauchen.

Häufig gestellte Fragen

Was ist Vererbung in C++?

Vererbung erlaubt es dir, eine neue Klasse (die abgeleitete Klasse) auf Basis einer vorhandenen (der Basisklasse) zu definieren. Die abgeleitete Klasse erhält automatisch die Datenmember und Memberfunktionen der Basis und kann neue hinzufügen oder vorhandenes Verhalten ersetzen. Sie modelliert eine „ist-ein"-Beziehung - ein Dog ist ein Animal - und ist der wichtigste Weg, wie C++ Code über Klassenhierarchien hinweg wiederverwendet und erweitert.

Was ist der Unterschied zwischen öffentlicher und privater Vererbung in C++?

Bei public-Vererbung (class Dog : public Animal) bleibt die öffentliche Schnittstelle der Basis auch in der abgeleiteten Klasse öffentlich, sodass ein Dog ein Animal ist und überall verwendet werden kann, wo ein Animal erwartet wird. Bei private-Vererbung werden die geerbten Member privat - die abgeleitete Klasse verwendet die Implementierung der Basis wieder, ist aber nicht für sie austauschbar. Öffentliche Vererbung ist bei Weitem der Normalfall; greife nur dann zur privaten, wenn es um eine Wiederverwendung im Sinne von „implementiert-mithilfe-von" geht.

In welcher Reihenfolge laufen Konstruktoren und Destruktoren bei Vererbung in C++?

Konstruktoren laufen von der Basis aus zuerst: Die Basisklasse wird vollständig konstruiert, bevor der Rumpf des abgeleiteten Konstruktors ausgeführt wird. Destruktoren laufen in genau umgekehrter Reihenfolge - erst die abgeleitete Klasse, dann die Basis. Das garantiert, dass jeder Teil, von dem ein abgeleitetes Objekt abhängt, während des Auf- oder Abbaus bereits existiert (oder noch existiert).

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S