Warum werden Konstruktoren nicht vererbt?

33

Ich bin verwirrt, was die Probleme sein könnten, wenn ein Konstruktor von einer Basisklasse geerbt wurde. Cpp Primer Plus sagt:

Konstruktoren unterscheiden sich von anderen Klassenmethoden darin, dass sie neue Objekte erstellen, während andere Methoden von vorhandenen Objekten aufgerufen werden . Dies ist einer der Gründe, warum Konstruktoren nicht vererbt werden . Vererbung bedeutet, dass ein abgeleitetes Objekt eine Basisklassenmethode verwenden kann. Bei Konstruktoren ist das Objekt jedoch erst vorhanden, nachdem der Konstruktor seine Arbeit erledigt hat.

Ich verstehe, dass der Konstruktor aufgerufen wird, bevor die Objektkonstruktion abgeschlossen ist.

Wie kann es zu Problemen kommen, wenn eine untergeordnete Klasse den übergeordneten Konstruktor erbt ( mit erbt ist gemeint, dass die untergeordnete Klasse die übergeordnete Klassenmethode usw. überschreiben kann. Nicht nur auf die übergeordnete Klassenmethode zugreifen kann )?

Ich verstehe, dass es nicht notwendig ist, einen Konstruktor explizit aus einem Code heraus aufzurufen [was mir noch nicht bekannt ist.], Außer beim Erstellen von Objekten. Selbst dann können Sie den übergeordneten Konstruktor [In cpp, using ::oder using member initialiser list, In java using super] mithilfe eines Mechanismus aufrufen . In Java gibt es eine Erzwingung, um es in der ersten Zeile aufzurufen. Ich verstehe, dass sichergestellt werden soll, dass zuerst das übergeordnete Objekt erstellt wird und dann die Konstruktion des untergeordneten Objekts fortgesetzt wird.

Es kann es außer Kraft setzen . Aber ich kann mir keine Situationen einfallen lassen, in denen dies ein Problem darstellen kann. Wenn das Kind den übergeordneten Konstruktor erbt, was kann schief gehen?

Dies dient nur dazu, nicht unnötige Funktionen zu erben. Oder steckt noch mehr dahinter?

Suvarna Pattayil
quelle
Mit C ++ 11 nicht wirklich auf dem neuesten Stand, aber ich denke, es gibt eine Art Konstruktorvererbung darin.
Yannis
3
Denken Sie daran, dass Sie, was auch immer Sie in Konstruktoren tun, in Destruktoren aufpassen müssen.
Manoj R
2
Ich denke, Sie müssen definieren, was unter "Erben eines Konstruktors" zu verstehen ist. Bei allen Antworten scheint es Unterschiede zu geben, was es bedeutet, einen Konstruktor zu erben. Ich denke, keiner von ihnen passt zu meiner ursprünglichen Interpretation. Die Beschreibung "in der grauen Box" von oben "Vererbung bedeutet, dass ein abgeleitetes Objekt eine Basisklassenmethode verwenden kann" impliziert, dass Konstruktoren vererbt werden. Ein abgeleitetes Objekt kann und verwendet mit Sicherheit IMMER Konstruktormethoden der Basisklasse.
Dunk
1
Vielen Dank für die Klarstellung, dass Sie einen Konstruktor geerbt haben. Jetzt können Sie einige Antworten erhalten.
Dunk

Antworten:

41

Es kann keine ordnungsgemäße Vererbung von Konstruktoren in C ++ geben, da der Konstruktor einer abgeleiteten Klasse zusätzliche Aktionen ausführen muss, die ein Basisklassenkonstruktor nicht ausführen muss und über die er nichts weiß. Diese zusätzlichen Aktionen sind die Initialisierung der Datenelemente der abgeleiteten Klasse (und in einer typischen Implementierung wird der vpointer auch so eingestellt, dass er auf die Tabelle der abgeleiteten Klassen verweist).

Wenn eine Klasse konstruiert wird, muss immer eine Reihe von Dingen passieren: Die Konstruktoren der Basisklasse (falls vorhanden) und die direkten Member müssen aufgerufen werden, und wenn es virtuelle Funktionen gibt, muss der vpointer korrekt gesetzt werden . Wenn Sie nicht über einen Konstruktor für die Klasse zur Verfügung stellen, wird der Compiler wird eine erstellen, die die erforderlichen Aktionen und sonst nichts führt. Wenn Sie tun einen Konstruktor bereitstellen, aber verpassen einige der erforderlichen Aktionen aus (zum Beispiel die Initialisierung einiger Mitglieder), dann wird der Compiler automatisch die fehlenden Aktionen zu Ihrem Konstruktor hinzuzufügen. Auf diese Weise stellt der Compiler sicher, dass jede Klasse über mindestens einen Konstruktor verfügt und jeder Konstruktor die von ihm erstellten Objekte vollständig initialisiert.

In C ++ 11 wurde eine Form der 'Konstruktorvererbung' eingeführt, mit der Sie den Compiler anweisen können, eine Reihe von Konstruktoren für Sie zu generieren, die dieselben Argumente wie die Konstruktoren aus der Basisklasse verwenden und diese Argumente einfach an die weiterleiten Basisklasse.
Obwohl es offiziell als Vererbung bezeichnet wird, ist es nicht wirklich so, da es immer noch eine spezifische Funktion für abgeleitete Klassen gibt. Jetzt wird es nur noch vom Compiler generiert, anstatt explizit von Ihnen geschrieben zu werden.

Diese Funktion funktioniert folgendermaßen:

struct Base {
    Base(int a) : i(a) {}
    int i;
};

struct Derived : Base {
    Derived(int a, std::string s) : Base(a), m(s) {}

    using Base::Base; // Inherit Base's constructors.
    // Equivalent to:
    //Derived(int a) : Base(a), m() {}

    std::string m;
};

Derivedhat jetzt zwei Konstruktoren (Kopier- / Verschiebekonstruktoren nicht eingerechnet). Eine, die ein Int und eine Zeichenfolge und eine, die nur ein Int nimmt.

Bart van Ingen Schenau
quelle
Unter der Annahme, dass die untergeordnete Klasse keinen eigenen Konstruktor hat? und der ererbte Konstruktor weiß offensichtlich nicht, welche
untergeordneten Elemente
C ++ ist so definiert, dass eine Klasse keinen eigenen Konstruktor haben kann. Deshalb habe ich nicht ‚Konstruktor Vererbung‘ betrachten , um tatsächlich sein Erbe. Es ist nur ein Weg, um zu vermeiden, dass mühsames Boilerplate geschrieben wird.
Bart van Ingen Schenau
2
Ich denke, die Verwirrung rührt von der Tatsache her, dass selbst ein leerer Konstruktor hinter den Kulissen arbeitet. Der Konstruktor besteht also aus zwei Teilen: der internen Arbeit und dem Teil, den Sie schreiben. Da sie nicht gut voneinander getrennt sind, werden Konstruktoren nicht vererbt.
Sarien
1
Bitte überlegen Sie, woher sie m()kommen und wie sie sich ändern würden, wenn es sich beispielsweise um einen Typ handeln würde int.
Deduplizierer
Ist es möglich, using Base::Baseimplizit zu tun ? Es hätte große Konsequenzen, wenn ich diese Zeile für eine abgeleitete Klasse vergessen hätte und den Konstruktor in allen abgeleiteten Klassen erben müsste
Post Self
7

Es ist nicht klar, was Sie unter "Erben des übergeordneten Konstruktors" verstehen. Sie verwenden das Wort Overriding , was darauf hindeutet, dass Sie an Konstruktoren denken , die sich wie polymorphe virtuelle Funktionen verhalten . Ich verwende den Begriff "virtuelle Konstruktoren" bewusst nicht, da dies ein gebräuchlicher Name für ein Codemuster ist, bei dem Sie tatsächlich eine bereits vorhandene Instanz eines Objekts benötigen, um eine andere zu erstellen.

Polymorphe Konstruktoren sind außerhalb des "virtuellen Konstruktor" -Musters wenig nützlich, und es ist schwierig, ein konkretes Szenario zu finden, in dem ein tatsächlicher polymorpher Konstruktor verwendet werden könnte. Ein sehr ausgeklügeltes Beispiel, das in keiner Weise auch nur entfernt gültiges C ++ ist :

struct Base {
  virtual Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) override {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Base(p1, p2); // This might call Derived(unsigned, unsigned).
  Derived *p_d2 = nullptr;
  p_d2 = new Base(p1, p2); // This might call Derived(unsigned, unsigned) too.
}

In diesem Fall hängt der aufgerufene Konstruktor vom konkreten Typ der zu erstellenden oder zuzuordnenden Variablen ab. Die Erkennung während der Syntaxanalyse / Codegenerierung ist komplex und hat kein Hilfsprogramm: Sie kennen den konkreten Typ, den Sie erstellen, und Sie haben einen speziellen Konstruktor für die abgeleitete Klasse geschrieben. Der folgende gültige C ++ - Code macht genau das Gleiche, ist etwas kürzer und ausführlicher:

struct Base {
  Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Derived(p1, p2); 
  Derived *p_d2 = nullptr;
  p_d2 = new Derived(p1, p2);
}


Eine zweite Interpretation oder möglicherweise zusätzliche Frage lautet: Was wäre, wenn die Basisklassenkonstruktoren automatisch in allen abgeleiteten Klassen vorhanden wären, sofern sie nicht explizit ausgeblendet werden?

Wenn das Kind den übergeordneten Konstruktor erbt, was kann schief gehen?

Sie müssten zusätzlichen Code schreiben, um einen übergeordneten Konstruktor auszublenden, der beim Erstellen der abgeleiteten Klasse nicht korrekt ist. Dies kann passieren, wenn die abgeleitete Klasse die Basisklasse so spezialisiert, dass bestimmte Parameter irrelevant werden.

Das typische Beispiel sind Rechtecke und Quadrate (Beachten Sie, dass Quadrate und Rechtecke im Allgemeinen nicht durch Liskov ersetzbar sind, so dass es kein sehr gutes Design ist, aber das Problem hervorhebt).

struct Rectangle {
  Rectangle(unsigned width, unsigned height) {...}
};

struct Square : public Rectangle {
  explicit Square(unsigned side) : Rectangle(side, side) {...}
};

Wenn Square den zweiwertigen Konstruktor von Rectangle geerbt hätte, könnten Sie Quadrate mit einer anderen Höhe und Breite konstruieren ... Das ist logisch falsch, also möchten Sie diesen Konstruktor ausblenden.

Joris Timmermans
quelle
3

Warum werden Konstruktoren nicht geerbt? Die Antwort ist überraschend einfach: Der Konstruktor der Basisklasse "baut" die Basisklasse und der Konstruktor der geerbten Klasse "baut" die geerbte Klasse. Wenn die geerbte Klasse den Konstruktor erben würde, würde der Konstruktor versuchen, ein Objekt vom Typ Basisklasse zu erstellen, und Sie könnten kein Objekt vom Typ geerbte Klasse "erstellen".

Mit welcher Art wird der Zweck der Vererbung einer Klasse besiegt?

Pieter B
quelle
3

Das offensichtlichste Problem beim Ermöglichen, dass die abgeleitete Klasse den Basisklassenkonstruktor überschreibt, besteht darin, dass der Entwickler der abgeleiteten Klasse nun dafür verantwortlich ist, zu wissen, wie seine Basisklasse (n) erstellt werden. Was passiert, wenn die abgeleitete Klasse die Basisklasse nicht ordnungsgemäß erstellt?

Außerdem würde das Liskov-Substitutionsprinzip nicht mehr gelten, da Sie sich nicht mehr darauf verlassen können, dass Ihre Sammlung von Basisklassenobjekten miteinander kompatibel ist, da keine Garantie dafür besteht, dass die Basisklasse ordnungsgemäß oder mit den anderen abgeleiteten Typen kompatibel konstruiert wurde.

Es ist weiter kompliziert, wenn mehr als eine Vererbungsebene hinzugefügt wird. Jetzt muss Ihre abgeleitete Klasse wissen, wie alle Basisklassen in der Kette aufgebaut werden.

Was passiert dann, wenn Sie eine neue Basisklasse an die Spitze der Vererbungshierarchie setzen? Sie müssten alle abgeleiteten Klassenkonstruktoren aktualisieren.

Dunk
quelle
2

Konstruktoren unterscheiden sich grundlegend von anderen Methoden:

  1. Sie werden generiert, wenn Sie sie nicht schreiben.
  2. Alle Basisklassenkonstruktoren werden implizit aufgerufen, auch wenn Sie dies nicht manuell tun
  3. Sie rufen sie nicht explizit auf, sondern indem Sie Objekte erstellen.

Warum werden sie also nicht vererbt? Einfache Antwort: Weil es immer einen Override gibt, entweder generiert oder manuell geschrieben.

Warum braucht jede Klasse einen Konstruktor? Das ist eine komplizierte Frage, und ich glaube, die Antwort ist vom Compiler abhängig. Es gibt so etwas wie einen "trivialen" Konstruktor, für den der Compiler nicht vorschreibt, dass er so genannt wird, wie es scheint. Ich denke, das ist das, was Sie unter Vererbung verstehen, aber aus den drei oben genannten Gründen halte ich den Vergleich von Konstruktoren mit normalen Methoden für nicht wirklich nützlich. :)

Sarien
quelle
1

Jede Klasse benötigt einen Konstruktor, auch die Standardkonstruktoren.
C ++ erstellt Standardkonstruktoren für Sie, es sei denn, Sie erstellen einen speziellen Konstruktor.
Für den Fall, dass Ihre Basisklasse einen spezialisierten Konstruktor verwendet, müssen Sie den spezialisierten Konstruktor für die abgeleitete Klasse schreiben, auch wenn beide identisch sind, und sie zurückketten.
Mit C ++ 11 können Sie Code-Duplikationen bei Konstruktoren vermeiden, indem Sie Folgendes verwenden :

Class A : public B {
using B:B;
....
  1. Eine Reihe von vererbenden Konstruktoren besteht aus

    • Alle Nicht-Template-Konstruktoren der Basisklasse (ggf. nach Weglassen von Auslassungspunkten) (seit C ++ 14)
    • Für jeden Konstruktor mit Standardargumenten oder dem Parameter ellipsis werden alle Konstruktorsignaturen, die durch Ablegen der Ellipse und Auslassen der Standardargumente an den Enden der Argumente gebildet werden, einzeln aufgelistet
    • Alle Konstruktorvorlagen der Basisklasse (ggf. nach Weglassen von Auslassungspunkten) (seit C ++ 14)
    • Für jede Konstruktorvorlage mit Standardargumenten oder der Ellipse werden alle Konstruktorsignaturen, die durch Ablegen der Ellipse und Auslassen der Standardargumente an den Enden der Argumente gebildet werden, einzeln aufgelistet
  2. Alle geerbten Konstruktoren, die nicht der Standardkonstruktor oder der Konstruktor copy / move sind und deren Signaturen nicht mit benutzerdefinierten Konstruktoren in der abgeleiteten Klasse übereinstimmen, werden implizit in der abgeleiteten Klasse deklariert. Die Standardparameter werden nicht vererbt

MeduZa
quelle
0

Sie können verwenden:

MyClass() : Base()

Fragen Sie sich, warum Sie das tun müssen?

Die Unterklasse verfügt möglicherweise über zusätzliche Eigenschaften, die möglicherweise im Konstruktor initialisiert werden müssen, oder initialisiert die Basisklassenvariablen auf andere Weise.

Wie sonst würden Sie das Untertypobjekt erstellen?

Tom Tom
quelle
Vielen Dank. Eigentlich versuche ich herauszufinden, warum ein Konstruktor nicht in der untergeordneten Klasse vererbt wird, wie bei anderen Methoden der übergeordneten Klasse.
Suvarna Pattayil