Warum haben wir keinen virtuellen Konstruktor in C ++?

Warum hat C ++ keinen virtuellen Konstruktor?

Höre es aus dem Maul des Pferdes :).

Von Bjarne Stroustrups C ++ Stil und Technik FAQ Warum haben wir keine virtuellen Konstruktoren?

Ein virtueller Aufruf ist ein Mechanismus, um bei teilweiser Information Arbeit zu erledigen. Insbesondere ermöglicht es “virtuell”, eine function aufzurufen, die nur irgendwelche Schnittstellen kennt und nicht den genauen Typ des Objekts. Um ein Objekt zu erstellen, benötigen Sie vollständige Informationen. Insbesondere müssen Sie den genauen Typ dessen, was Sie erstellen möchten, kennen. Folglich kann ein “Aufruf an einen Konstruktor” nicht virtuell sein.

Der FAQ-Eintrag geht weiter, um dem Code einen Weg zu geben, dieses Ziel ohne einen virtuellen Konstruktor zu erreichen.

Virtuelle functionen bieten grundsätzlich polymorphes Verhalten. Das heißt, wenn Sie mit einem Objekt arbeiten, dessen dynamischer Typ sich von dem statischen (Kompilierzeit) -Typ unterscheidet, auf den es verweist, bietet es ein Verhalten, das für den tatsächlichen Typ des Objekts anstelle des statischen Typs des Objekts geeignet ist.

Versuchen Sie nun, dieses Verhalten auf einen Konstruktor anzuwenden. Wenn Sie ein Objekt konstruieren, ist der statische Typ immer gleich dem tatsächlichen Objekttyp seit:

Um ein Objekt zu konstruieren, benötigt ein Konstruktor den genauen Typ des Objekts, das er erzeugen soll. […] Außerdem können Sie keinen pointers auf einen Konstruktor haben

(Bjarne Stroustup (P424 Die C ++ Programmiersprache SE))

Im Gegensatz zu objektorientierten Sprachen wie Smalltalk oder Python ist der Konstruktor eine virtuelle Methode des Objekts, das die class repräsentiert (was bedeutet, dass Sie das abstrakte Factory-Muster von GoF nicht benötigen, da Sie das Objekt, das die class repräsentiert, weitergeben können C ++ ist eine klassenbasierte Sprache und enthält keine Objekte, die eines der Konstrukte der Sprache repräsentieren. Die class existiert zur Laufzeit nicht als Objekt, Sie können also keine virtuelle Methode darauf aufrufen.

Dies passt zu der Philosophie “Sie zahlen nicht für das, was Sie nicht verwenden”, obwohl jedes große C ++ – Projekt, das ich gesehen habe, letztendlich eine Form abstrakter Factory oder Reflektion implementiert hat.

zwei Gründe, die ich mir vorstellen kann:

Technischer Grund

Das Objekt existiert erst, nachdem der Konstruktor beendet wurde. Damit der Konstruktor über die virtuelle Tabelle ausgetriggers wird, muss ein vorhandenes Objekt mit einem pointers auf die virtuelle Tabelle vorhanden sein, aber wie kann ein pointers auf die virtuelle Tabelle existieren, wenn das Objekt vorhanden ist existiert noch nicht? 🙂

Logikgrund

Sie verwenden das Schlüsselwort virtual, wenn Sie ein etwas polymorphes Verhalten deklarieren möchten. Aber es gibt nichts, das mit Konstruktoren polymorph ist, Konstruktorarbeit in C ++ ist, einfach eine Objektdaten in den Speicher zu legen. Da virtuelle Tabellen (und Polymorphismen im Allgemeinen) sich allesamt auf polymorphes Verhalten und nicht auf polymorphe Daten beziehen, hat es keinen Sinn, einen virtuellen Konstruktor zu deklarieren.

Wir machen es, es ist einfach kein Konstruktor 🙂

 struct A { virtual ~A() {} virtual A * Clone() { return new A; } }; struct B : public A { virtual A * Clone() { return new B; } }; int main() { A * a1 = new B; A * a2 = a1->Clone(); // virtual construction delete a2; delete a1; } 

Abgesehen von semantischen Gründen gibt es keine vtable, bis das Objekt konstruiert ist, was eine virtuelle Bezeichnung nutzlos macht.

Zusammenfassung : Der C ++ Standard könnte eine Notation und ein Verhalten für “virtuelle Konstruktoren” angeben, die einigermaßen intuitiv und nicht zu schwer für Compiler zu unterstützen sind, aber warum sollte eine Standardänderung speziell dann vorgenommen werden, wenn die functionalität bereits mit create() sauber implementiert werden kann. / clone() (siehe unten)? Es ist nicht annähernd so nützlich wie viele andere Sprachvorschläge in der Pipeline.

Diskussion

Lassen Sie uns einen “virtuellen Konstruktor” -Mechanismus postulieren:

 Base* p = new Derived(...); Base* p2 = new p->Base(); // possible syntax??? 

Im obigen erzeugt die erste Zeile ein Derived Objekt, so dass die virtuelle Abfertigungstabelle von *p vernünftigerweise einen “virtuellen Konstruktor” zur Verwendung in der zweiten Zeile liefern kann. (Dutzende Antworten auf dieser Seite, die besagen, dass “das Objekt noch nicht existiert, so dass virtuelle Konstruktion unmöglich ist” sind unnötigerweise myopisch auf das zu konstruierende Objekt gerichtet.)

Die zweite Zeile postuliert die Notation new p->Base() , um die dynamische Zuweisung und die Standardkonstruktion eines anderen Derived Objekts anzufordern.

Anmerkungen:

  • Der Compiler muss die Speicherzuweisung vor dem Aufrufen des Konstruktors orchestrieren – Konstruktoren unterstützen normalerweise die automatische (informell “Stapel”) Zuordnung, statisch (für globalen / Namespace-Bereich und classn- / functions- static Objekte) und dynamisch (informell “Heap”) wird eingesetzt

    • Die Größe des Objekts, das von p->Base() kann zur Kompilierungszeit nicht allgemein bekannt sein. Daher ist die dynamische Zuweisung der einzige sinnvolle Ansatz

      • es ist möglich, Laufzeit-spezifizierte Speichermengen auf dem Stack zuzuordnen – zB alloca() Array-Erweiterung mit variabler Länge , alloca() – führt jedoch zu erheblichen Ineffizienzen und Komplexitäten (zB hier und hier )
  • Für die dynamische Zuweisung muss ein pointers zurückgegeben werden, damit der Speicher später delete kann.

  • Die postulierte Notation listet explizit new auf, um die dynamische Zuweisung und den Pointer-Ergebnistyp hervorzuheben.

Der Compiler müsste:

  • Finden Sie heraus, wie viel Speicher Derived benötigt, entweder indem Sie eine implizite virtual sizeof function aufrufen oder solche Informationen über RTTI verfügbar haben
  • Aufruf des operator new(size_t) um Speicher zu reservieren
  • Rufen Sie Derived() mit new Platzierung auf.

ODER

  • Erstellen Sie einen zusätzlichen vtable-Eintrag für eine function, die dynamische Zuweisung und Konstruktion kombiniert

Also – es scheint nicht unüberwindbar zu sein, virtuelle Konstruktoren zu spezifizieren und zu implementieren, aber die Millionen-Dollar-Frage lautet: Wie wäre es besser als das, was mit den vorhandenen C ++ – Sprachfunktionen möglich ist …? Persönlich sehe ich keinen Vorteil gegenüber der folgenden Lösung.


`clone ()` und `create ()`

Die C ++ – FAQ dokumentiert ein “virtuelles Konstruktor” -Idiom , das virtual create() und clone() -Methoden enthält, um ein neues dynamisch zugewiesenes Objekt standardmäßig zu konstruieren oder zu kopieren:

 class Shape { public: virtual ~Shape() { } // A virtual destructor virtual void draw() = 0; // A pure virtual function virtual void move() = 0; // ... virtual Shape* clone() const = 0; // Uses the copy constructor virtual Shape* create() const = 0; // Uses the default constructor }; class Circle : public Shape { public: Circle* clone() const; // Covariant Return Types; see below Circle* create() const; // Covariant Return Types; see below // ... }; Circle* Circle::clone() const { return new Circle(*this); } Circle* Circle::create() const { return new Circle(); } 

Es ist auch möglich, create() zu ändern oder zu überladen create() um Argumente zu akzeptieren. Um jedoch der virtual functionssignatur der Basisklasse / Schnittstelle zu entsprechen, müssen Argumente für Überschreibungen genau mit einer der Überladungen der Basisklasse übereinstimmen. Mit diesen expliziten, vom Benutzer bereitgestellten functionen können Logging, Instrumentierung, Speicherzuordnung usw. einfach hinzugefügt werden.

Obwohl das Konzept der virtuellen Konstruktoren nicht gut passt, da der Objekttyp die Voraussetzung für die Objekterstellung ist, ist es nicht vollständig überbestimmt.

GOFs Entwurfsmuster “Factory-Methode” nutzt das “Konzept” des virtuellen Konstruktors, das sich in bestimmten Design-Situationen befindet.

Virtuelle functionen werden verwendet, um functionen basierend auf dem Objekttyp, auf den der pointers zeigt, und nicht den Typ des pointerss selbst aufzurufen. Aber ein Konstruktor wird nicht “aufgerufen”. Es wird nur einmal aufgerufen, wenn ein Objekt deklariert wird. Ein Konstruktor kann daher in C ++ nicht virtuell gemacht werden.

Sie sollten auch keine virtuelle function innerhalb Ihres Konstruktors aufrufen. Siehe: http://www.artima.com/cppsource/nevercall.html

Außerdem bin ich mir nicht sicher, ob Sie wirklich einen virtuellen Konstruktor benötigen. Sie können polymorphe Konstruktion ohne es erreichen: Sie können eine function schreiben, die Ihr Objekt gemäß den erforderlichen Parametern konstruieren wird.

Wenn Menschen eine solche Frage stellen, denke ich mir gerne “Was würde passieren, wenn das tatsächlich möglich wäre?” Ich weiß nicht wirklich, was das bedeuten würde, aber ich denke, es würde etwas damit zu tun haben, die Konstruktorimplementierung basierend auf dem dynamischen Typ des zu erstellenden Objekts zu überschreiben.

Ich sehe darin eine Reihe potenzieller Probleme. Zum einen wird die abgeleitete class zu dem Zeitpunkt, zu dem der virtuelle Konstruktor aufgerufen wird, nicht vollständig konstruiert, so dass es potentielle Probleme mit der Implementierung gibt.

Zweitens, was würde im Falle der Mehrfachvererbung passieren? Ihr virtueller Konstruktor würde vermutlich mehrmals aufgerufen werden, dann müssten Sie wissen, welcher Aufruf aufgerufen wurde.

Drittens, im Allgemeinen zum Zeitpunkt der Konstruktion, hat das Objekt die virtuelle Tabelle nicht vollständig aufgebaut, das bedeutet, es würde eine große Änderung der Sprachspezifikation erfordern, um der Tatsache Rechnung zu tragen, dass der dynamische Typ des Objekts bei der Konstruktion bekannt wäre Zeit. Dies würde dann ermöglichen, dass der Basisklassenkonstruktor möglicherweise andere virtuelle functionen zur Konstruktionszeit mit einem nicht vollständig konstruierten dynamischen classntyp aufruft.

Schließlich, wie jemand anderes gezeigt hat, können Sie eine Art virtueller Konstruktor implementieren, der statische “create” – oder “init” -functionen verwendet, die im Grunde dasselbe tun, wie es ein virtueller Konstruktor tun würde.

Virtuelle functionen in C ++ sind eine Implementierung von Laufzeitpolymorphismus, und sie funktionieren übersteuernd. Im Allgemeinen wird das virtual Schlüsselwort in C ++ verwendet, wenn Sie dynamisches Verhalten benötigen. Es funktioniert nur, wenn das Objekt existiert. Während Konstruktoren zum Erstellen der Objekte verwendet werden. Konstruktoren werden zum Zeitpunkt der Objekterstellung aufgerufen.

Wenn Sie also den Konstruktor gemäß der virtuellen Schlüsselwortdefinition als virtual erstellen, sollte das vorhandene Objekt vorhanden sein, aber der Konstruktor wird verwendet, um das Objekt zu erstellen, so dass dieser Fall niemals existiert. Sie sollten den Konstruktor also nicht als virtuell verwenden.

Wenn wir also versuchen, den virtuellen Konstruktor-Compiler zu deklarieren, casting wir einen Fehler:

Konstruktoren können nicht als virtuell deklariert werden

Sie können ein Beispiel und den technischen Grund finden, warum es in @stefans Antwort nicht erlaubt ist. Nun eine logische Antwort auf diese Frage ist meiner Meinung nach:

Die Hauptverwendung von virtuellem Schlüsselwort besteht darin, polymorphes Verhalten zu aktivieren, wenn wir nicht wissen, auf welchen Typ des Objekts der pointers der Basisklasse zeigt.

Aber denken Sie daran ist primitiver Weg, für die Verwendung virtueller functionalität benötigen Sie einen pointers. Und was benötigt ein pointers? Ein Objekt zum Zeigen! (unter Berücksichtigung der korrekten Ausführung des Programms)

Wir benötigen also grundsätzlich ein Objekt, das bereits irgendwo im Speicher vorhanden ist (wir befassen uns nicht damit, wie der Speicher zugewiesen wurde, es kann zur Kompilierungszeit oder zur Laufzeit sein), so dass unser pointers korrekt auf dieses Objekt zeigen kann.

Denken Sie nun an die Situation in dem Moment, in dem dem Objekt der zu spitzenden class etwas Speicher zugewiesen wird -> Sein Konstruktor wird automatisch an dieser Instanz selbst aufgerufen!

So können wir sehen, dass wir uns nicht wirklich darum sorgen müssen, dass der Konstruktor virtuell ist, denn in jedem Fall, in dem Sie ein polymorphes Verhalten verwenden möchten, wäre unser Konstruktor bereits ausgeführt worden, um unser Objekt für die Verwendung bereit zu machen!

Der virtuelle Mechanismus funktioniert nur, wenn Sie einen classnzeiger auf ein abgeleitetes classnobjekt haben. Die Konstruktion hat eigene Regeln für den Aufruf von Basisklassenkonstruktoren, im Grunde Basisklasse, um abgeleitet zu werden. Wie könnte ein virtueller Konstruktor nützlich oder angerufen werden? Ich weiß nicht, was andere Sprachen tun, aber ich kann nicht sehen, wie ein virtueller Konstruktor nützlich oder sogar implementiert werden könnte. Es muss eine Konstruktion stattgefunden haben, damit der virtuelle Mechanismus Sinn macht und eine Konstruktion auch für die Vtable-Strukturen erstellt werden muss, die geschaffen wurden, die die Mechanik des polymorphen Verhaltens bereitstellen.

Können wir es einfach nicht sagen … Wir können Konstruktoren nicht erben. Es hat also keinen Sinn, sie als virtuell zu deklarieren, weil das Virtuelle Polymorphismus bietet.

Eine virtuelle Tabelle (vtable) wird für jede class mit einer oder mehreren “virtuellen functionen” erstellt. Wenn ein Objekt aus einer solchen class erzeugt wird, enthält es einen ‘virtuellen pointers’, der auf die Basis der entsprechenden vtable zeigt. Immer wenn ein virtueller functionsaufruf vorliegt, wird die vtable zur Auflösung der functionsadresse verwendet. Der Konstruktor kann nicht virtuell sein, denn wenn der Konstruktor einer class ausgeführt wird, gibt es keine vtable im Speicher, dh es ist noch kein virtueller pointers definiert. Daher sollte der Konstruktor immer nicht virtuell sein.

Ein virtueller C ++ – Konstruktor ist nicht möglich. Beispielsweise können Sie einen Konstruktor nicht als virtuell kennzeichnen. Versuchen Sie diesen Code

 #include using namespace std; class aClass { public: virtual aClass() { } }; int main() { aClass a; } 

Es verursacht einen Fehler. Dieser Code versucht, einen Konstruktor als virtuell zu deklarieren. Versuchen wir nun zu verstehen, warum wir ein virtuelles Schlüsselwort verwenden. Das virtuelle Schlüsselwort wird verwendet, um einen Laufzeitpolymorphismus bereitzustellen. Versuchen Sie zum Beispiel diesen Code.

 #include using namespace std; class aClass { public: aClass() { cout< <"aClass contructor\n"; } ~aClass() { cout<<"aClass destructor\n"; } }; class anotherClass:public aClass { public: anotherClass() { cout<<"anotherClass Constructor\n"; } ~anotherClass() { cout<<"anotherClass destructor\n"; } }; int main() { aClass* a; a=new anotherClass; delete a; getchar(); } 

In Haupt a=new anotherClass; reserviert einen Speicher für anotherClass in einem pointers, der als Typ von aClass deklariert aClass bewirkt, dass der Konstruktor (In aClass und anotherClass ) automatisch aClass anotherClass wir den Konstruktor nicht als virtuell markieren. Denn wenn ein Objekt erstellt wird, muss es folgen die Kette der Schöpfung (dh zuerst die Basis und dann die abgeleiteten classn). Aber wenn wir versuchen, ein delete a; zu delete a; Es ruft nur den Basisdestruktor auf. Daher müssen wir den Destruktor mit einem virtuellen Schlüsselwort behandeln. Also virtueller Konstruktor ist nicht möglich, aber virtueller Destruktor ist. Danke

Es gibt einen sehr grundlegenden Grund: Konstruktoren sind effektiv statische functionen, und in C ++ kann keine statische function virtuell sein.

Wenn Sie viel Erfahrung mit C ++ haben, wissen Sie alles über den Unterschied zwischen statischen und Member-functionen. Statische functionen sind der CLASS zugeordnet, nicht die Objekte (Instanzen), sodass sie keinen “This” -pointers sehen. Nur Member-functionen können virtuell sein, weil die vtable – die versteckte Tabelle von functionszeigern, die “virtuell” arbeiten – wirklich ein Datenelement jedes Objekts ist.

Nun, was macht der Konstrukteur? Es ist im Namen – ein “T” -Konstruktor initialisiert T-Objekte, wie sie zugewiesen sind. Dies schließt automatisch aus, dass es eine Mitgliedsfunktion ist! Ein Objekt muss existieren, bevor es einen “This” -pointers und damit eine Vtable hat. Das bedeutet, dass selbst wenn die Sprache Konstruktoren als gewöhnliche functionen behandelt (aus Gründen, auf die ich nicht eingehen kann), sie statische Elementfunktionen sein müssten.

Eine gute Möglichkeit, dies zu sehen, ist das “Factory” -Muster, insbesondere Factory-functionen. Sie tun, wonach Sie suchen, und Sie werden feststellen, dass es, wenn class T eine Factory-Methode hat, IMMER STATISCH ist. Es muss sein.

Der VPointer wird zum Zeitpunkt der Objekterstellung erstellt. vpointer existiert nicht vor der Objekterstellung. Es macht also keinen Sinn, den Konstruktor als virtuell zu definieren.

Wenn Sie logisch darüber nachdenken, wie Konstruktoren arbeiten und was die Bedeutung / Verwendung einer virtuellen function in C ++ bedeutet, werden Sie feststellen, dass ein virtueller Konstruktor in C ++ bedeutungslos wäre. Das Deklarieren von etwas virtuellem in C ++ bedeutet, dass es von einer Unterklasse der aktuellen class überschrieben werden kann, jedoch wird der Konstruktor aufgerufen, wenn das Objekt erstellt wird. Zu diesem Zeitpunkt können Sie keine Unterklasse der class erstellen Erstellen der class, so dass es niemals nötig wäre, einen Konstruktor virtuell zu deklarieren.

Und ein anderer Grund ist, dass die Konstruktoren den gleichen Namen wie der classnname haben und wenn wir den Konstruktor als virtuell deklarieren, dann sollte er in seiner abgeleiteten class mit demselben Namen neu definiert werden, aber Sie dürfen nicht den gleichen Namen zweier classn haben. Es ist also nicht möglich, einen virtuellen Konstruktor zu haben.