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?
quelle
Antworten:
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:
Derived
hat jetzt zwei Konstruktoren (Kopier- / Verschiebekonstruktoren nicht eingerechnet). Eine, die ein Int und eine Zeichenfolge und eine, die nur ein Int nimmt.quelle
m()
kommen und wie sie sich ändern würden, wenn es sich beispielsweise um einen Typ handeln würdeint
.using Base::Base
implizit 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üssteEs 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 :
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:
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?
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).
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.
quelle
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?
quelle
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.
quelle
Konstruktoren unterscheiden sich grundlegend von anderen Methoden:
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. :)
quelle
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 :
Eine Reihe von vererbenden Konstruktoren besteht aus
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
quelle
Sie können verwenden:
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?
quelle