Einleitung: Warum Elternkonstruktoren ein Minenfeld für Anfänger sind
Jeder, der angefangen hat, Vererbung in C++ zu lernen, ist früher oder später auf folgende Situation gestoßen: Sie erstellen eine abgeleitete Klasse, der Compiler meckert, das Programm wird nicht kompiliert, und Sie verstehen nicht – warum? Meistens liegt die Ursache des Übels im falschen Aufruf des Basisklassenkonstruktors. Diese Frage hat auf Stack Overflow tausende Aufrufe gesammelt, und das zu Recht: Selbst erfahrene Entwickler vergessen manchmal die grundlegenden Regeln.
In diesem Artikel analysieren wir die Regeln zum Aufruf des Basisklassenkonstruktors in C++, gehen die Top-5 der häufigsten Fehler durch und zeigen, wie man sie vermeidet. Wenn Sie zuverlässigen Code ohne Überraschungen schreiben möchten – lesen Sie bis zum Ende.
Kurzer Crashkurs: Wie Konstruktoren bei der Vererbung funktionieren
In C++ werden Konstruktoren nicht vererbt. Wenn Sie ein Objekt einer abgeleiteten Klasse erstellen, wird zuerst der Konstruktor der Basisklasse (implizit oder explizit) aufgerufen und dann der Konstruktor der abgeleiteten Klasse. Wenn Sie nicht angeben, welcher Konstruktor der Elternklasse aufgerufen werden soll, versucht der Compiler, den Standardkonstruktor der Basisklasse aufzurufen. Wenn es keinen gibt, erhalten Sie einen Kompilierungsfehler.
Der explizite Aufruf erfolgt über die Initialisierungsliste:
class Base {public: Base(int x) { /* ... */ }};
class Derived : public Base {public: // Richtig: expliziter Aufruf des Konstruktors Base(int) Derived() : Base(42) { /* ... */ }};Scheint einfach? Aber genau hier machen Anfänger 90% der Fehler. Lassen Sie uns sie der Reihe nach durchgehen.
Top-5 Fehler beim Aufruf des Basisklassenkonstruktors
Fehler Nr. 1: Vergessen, den Elternkonstruktor mit Parametern aufzurufen
Das häufigste Problem. Wenn die Basisklasse keinen Standardkonstruktor hat und Sie ihn nicht explizit aufrufen – gibt der Compiler einen Fehler aus.
class Base {public: Base(int x) { /* Initialisierung */ }};
class Derived : public Base {public: // FEHLER: Compiler versucht Base() aufzurufen, das es nicht gibt Derived() { }};So beheben Sie es:
class Derived : public Base {public: // RICHTIG: explizit Base(int) aufrufen Derived() : Base(0) { } // Oder Parameter von außen übergeben: Derived(int x) : Base(x) { }};Fehler Nr. 2: Falsche Initialisierungsreihenfolge
Viele denken, dass die Reihenfolge der Konstruktoraufrufe in der Initialisierungsliste die Ausführungsreihenfolge bestimmt. Das ist nicht der Fall. Die Basisklasse wird immer zuerst initialisiert, unabhängig davon, wo Sie ihren Aufruf geschrieben haben.
class Derived : public Base {private: int value;public: // Scheint, als ob value zuerst initialisiert wird? Nein! Derived() : value(10), Base(5) { } // Tatsächlich: zuerst Base(5), dann value(10)};Dies kann zu logischen Fehlern führen, wenn die Initialisierung von value vom Zustand der Basisklasse abhängt.
Fehler Nr. 3: Aufruf des Elternkonstruktors im Konstruktorrumpf
Einige Anfänger versuchen, den Basisklassenkonstruktor innerhalb der geschweiften Klammern wie eine normale Methode aufzurufen. Das funktioniert nicht – ein Konstruktor kann nur in der Initialisierungsliste aufgerufen werden.
class Derived : public Base {public: Derived() { // FEHLER: So wird der Konstruktor nicht aufgerufen Base(10); // Erstellt nur ein temporäres Objekt und löscht es }};Fehler Nr. 4: Verwechslung mit Kopierkonstruktoren
Bei der Implementierung des Kopierkonstruktors in einer abgeleiteten Klasse muss der Kopierkonstruktor der Basisklasse explizit aufgerufen werden. Andernfalls wird der Standardkonstruktor aufgerufen und die Daten der Basisklasse werden nicht kopiert.
class Derived : public Base {public: // FEHLER: Base wird falsch kopiert Derived(const Derived& other) { /* ... */ }
// RICHTIG: Derived(const Derived&