web-dev-qa-db-ger.com

Warum haben wir keinen virtuellen Konstruktor in C++?

Warum hat C++ keinen virtuellen Konstruktor?

220
Arjun

Hören Sie es aus dem Munde des Pferdes :). 

Von Bjarne Stroustrup's FAQs zu C++ - Stil und Technik Warum haben wir keine virtuellen Konstruktoren?

Ein virtueller Anruf ist ein Mechanismus, um die Arbeit teilweise zu erledigen. Information. Insbesondere ermöglicht "virtuell" das Aufrufen einer Funktion Sie kennen nur beliebige Schnittstellen und nicht den genauen Typ des Objekts. Zu Erstellen Sie ein Objekt, für das Sie vollständige Informationen benötigen. Insbesondere Sie Sie müssen wissen, was genau Sie erstellen möchten. Folglich, Ein "Aufruf an einen Konstruktor" kann nicht virtuell sein.

Der Eintrag FAQ gibt den Code an, um dieses Ziel ohne einen virtuellen Konstruktor zu erreichen.

212
aJ.

Virtuelle Funktionen bieten grundsätzlich polymorphes Verhalten. Wenn Sie also mit einem Objekt arbeiten, dessen dynamischer Typ sich vom statischen Typ (Kompilierzeit) unterscheidet, mit dem es bezeichnet wird, bietet es ein Verhalten, das für den Objekttyp actual des Objekts anstelle des statischen Typs geeignet ist des Objekts.

Versuchen Sie nun, diese Art von Verhalten auf einen Konstruktor anzuwenden. Wenn Sie ein Objekt erstellen, stimmt der statische Typ immer mit dem tatsächlichen Objekttyp überein, da:

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

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

119
Anton Gogolev

Im Gegensatz zu objektorientierten Sprachen wie Smalltalk oder Python, bei denen der Konstruktor eine virtuelle Methode des Objekts ist, das die Klasse darstellt (was bedeutet, dass Sie kein GoF abstraktes Factory-Muster benötigen, da Sie das die Klasse repräsentierende Objekt übergeben können C++ ist eine klassenbasierte Sprache, und es gibt keine Objekte, die eines der Konstrukte der Sprache darstellen. Die Klasse ist zur Laufzeit nicht als Objekt vorhanden. Sie können daher keine virtuelle Methode 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 irgendeine Form abstrakter Fabrik oder Reflexion implementiert hat.

57
Pete Kirkham

zwei Gründe, die mir einfallen können:

Technischer Grund

Das Objekt ist erst nach dem Ende des Konstruktors vorhanden. Damit der Konstruktor mithilfe der virtuellen Tabelle ausgelöst werden kann, muss ein vorhandenes Objekt mit einem Zeiger auf die virtuelle Tabelle vorhanden sein. Wie kann jedoch ein Zeiger auf die virtuelle Tabelle des Objekts vorhanden sein existiert noch nicht? :)

Logischer Grund

Sie verwenden das virtuelle Schlüsselwort, wenn Sie ein etwas polymorphes Verhalten deklarieren möchten. Konstruktoren sind jedoch nichts Polymorphes. Konstruktoren müssen in C++ einfach Objektdaten in den Speicher schreiben. Da es sich bei virtuellen Tabellen (und Polymorphismen im Allgemeinen) nur um polymorphes Verhalten und nicht um polymorphe Daten handelt, ist es nicht sinnvoll, einen virtuellen Konstruktor zu deklarieren.

39
user88637

Abgesehen von den semantischen Gründen gibt es keine Tabelle, nachdem das Objekt erstellt wurde, wodurch eine virtuelle Bezeichnung unbrauchbar wird.

13
Marius

Zusammenfassung : Der C++ - Standard könnte angeben: a Notation und Verhalten für "virtuelle Konstruktoren" sind einigermaßen intuitiv und für Compiler nicht allzu schwer zu unterstützen, aber warum sollten Sie eine Standardänderung vornehmen, wenn die -Funktionalität bereits sauber implementiert werden kann? mit create()/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???

In der obigen Abbildung wird in der ersten Zeile ein Derived - Objekt erstellt, sodass in der virtuellen Dispatch-Tabelle von *p Ein "virtueller Konstruktor" zur Verwendung in der zweiten Zeile bereitgestellt werden kann. (Dutzende Antworten auf dieser Seite, die besagen "Das Objekt existiert noch nicht, so dass eine virtuelle Konstruktion unmöglich ist" , konzentrieren sich unnötigerweise kurzsichtig auf das zu konstruierende Objekt.)

In der zweiten Zeile wird die Notation new p->Base() postuliert, um die dynamische Zuordnung und die Standardkonstruktion eines anderen Objekts Derived anzufordern.

Anmerkungen:

  • Der Compiler muss die Speicherzuordnung koordinieren, bevor er den Konstruktor aufruft. - Konstruktoren unterstützen normalerweise automatische (informell "Stapel") Zuordnung, statische (für globale/Namespace-Bereiche und Klassen-/Funktionsobjekte - static) und dynamic (informell "Heap"), wenn new verwendet wird

    • die Größe des Objekts, das mit p->Base() erstellt werden soll, ist zum Zeitpunkt der Kompilierung im Allgemeinen nicht bekannt. Daher ist die dynamische Zuordnung der einzige Ansatz Sinn

  • für die dynamische Zuweisung muss einen Zeiger zurückgeben , damit der Speicher später delete sein kann.

  • die postulierte Notation listet explizit new auf, um die dynamische Zuordnung und den Zeiger-Ergebnistyp hervorzuheben.

Der Compiler müsste:

  • finden Sie heraus, wie viel Speicher Derived benötigt wird, indem Sie entweder eine implizite Funktion virtualsizeof aufrufen oder solche Informationen über RTTI verfügbar machen
  • rufen Sie operator new(size_t) auf, um Speicher zuzuweisen
  • rufen Sie Derived() mit Platzierung new auf.

OR

  • erstellen Sie einen zusätzlichen Vtable-Eintrag für eine Funktion, die dynamische Zuordnung und Konstruktion kombiniert

Also - es scheint nicht unüberwindlich, virtuelle Konstruktoren zu spezifizieren und zu implementieren, aber die millionenschwere Frage lautet: Wie wäre es besser, als was mit 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 standardmäßig die Methoden virtualcreate() und clone() enthält -Konstruiere oder kopiere ein neues dynamisch zugewiesenes Objekt:

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, um Argumente zu akzeptieren. Um jedoch mit der Funktionssignatur virtual der Basisklasse/Schnittstelle übereinzustimmen, müssen die zu überschreibenden Argumente genau mit einer der Überladungen der Basisklasse übereinstimmen. Mit diesen expliziten vom Benutzer bereitgestellten Funktionen ist es einfach, Protokollierung, Instrumentierung, Speicherzuweisung usw. hinzuzufügen.

13
Tony Delroy

Wir machen, 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;
}
13
anon

Obwohl das Konzept der virtuellen Konstruktoren nicht gut passt, da der Objekttyp Voraussetzung für die Objekterstellung ist, wird es nicht völlig überbewertet.

GOFs "Factory-Methode" -Designmuster verwendet das "Konzept" des virtuellen Konstruktors, das in bestimmten Designsituationen von Vorteil ist.

5
Shraddha Sinha

Virtuelle Funktionen in C++ sind eine Implementierung des Laufzeit-Polymorphismus, und diese werden überschrieben. Im Allgemeinen wird das Schlüsselwort virtual in C++ verwendet, wenn Sie dynamisches Verhalten benötigen. Es funktioniert nur, wenn ein Objekt vorhanden ist. Während Konstruktoren zum Erstellen der Objekte verwendet werden. Konstruktoren werden zum Zeitpunkt der Objekterstellung aufgerufen.

Wenn Sie also den Konstruktor als virtual gemäß der Definition der virtuellen Schlüsselwörter erstellen, sollte das vorhandene Objekt vorhanden sein. Der Konstruktor wird jedoch zum Erstellen des Objekts verwendet, sodass dieser Fall niemals existiert. Daher sollten Sie den Konstruktor nicht als virtuell verwenden.

Wenn wir also versuchen, den Compiler des virtuellen Konstruktors zu deklarieren, werfen Sie einen Fehler: 

Konstruktoren können nicht als virtuell deklariert werden

5
Neha kumari

Virtuelle Funktionen werden verwendet, um Funktionen basierend auf dem Objekttyp aufzurufen, auf den der Zeiger zeigt, und nicht auf dem Zeigertyp selbst. Ein Konstruktor wird jedoch nicht "aufgerufen". Es wird nur einmal aufgerufen, wenn ein Objekt deklariert wird. Daher kann ein Konstruktor in C++ nicht virtuell gemacht werden.

4
skrtbhtngr

In der Antwort von @stefan finden Sie ein Beispiel und den technischen Grund dafür, warum dies nicht zulässig ist. Eine logische Antwort auf diese Frage lautet meiner Meinung nach:

Virtuelle Schlüsselwörter werden hauptsächlich verwendet, um polymorphes Verhalten zu aktivieren, wenn wir nicht wissen, auf welchen Typ des Objekts der Basisklassenzeiger zeigt.

Aber denken Sie daran, dies ist eine primitivere Art. Für die Verwendung der virtuellen Funktionalität benötigen Sie einen Zeiger. Und was braucht ein Zeiger? Ein Objekt, auf das Sie zeigen können! (Berücksichtigung der korrekten Ausführung des Programms)

Wir benötigen also im Grunde ein Objekt, das sich bereits irgendwo im Speicher befindet (es ist nicht wichtig, wie der Speicher zugewiesen wurde, er kann sich zum Zeitpunkt der Kompilierung oder einer der Laufzeit befinden), damit unser Zeiger korrekt auf dieses Objekt zeigen kann.

Stellen Sie sich nun die Situation vor, wenn dem Objekt der zu zeigenden Klasse Speicherplatz zugewiesen wird -> Sein Konstruktor wird automatisch an dieser Instanz selbst aufgerufen!

Wir können also sehen, dass wir uns nicht wirklich darum kümmern 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.

4
Kushan Mehta

Für jede Klasse mit einer oder mehreren virtuellen Funktionen wird eine virtuelle Tabelle (vtable) erstellt. Immer wenn ein Objekt aus einer solchen Klasse erstellt wird, enthält es einen "virtuellen Zeiger", der auf die Basis der entsprechenden vtable zeigt. Bei jedem virtuellen Funktionsaufruf wird die vtable zur Auflösung der Funktionsadresse ..__ verwendet. Der Konstruktor kann nicht virtuell sein, da bei der Ausführung eines Konstruktors einer Klasse keine vtable im Speicher vorhanden ist. Daher sollte der Konstruktor immer nicht virtuell sein.

3
Navneet Agarwal

Sie sollten auch keine virtuelle Funktion in Ihrem Konstruktor aufrufen. Siehe: http://www.artima.com/cppsource/nevercall.html

Darüber hinaus bin ich mir nicht sicher, ob Sie wirklich einen virtuellen Konstruktor benötigen. Sie können eine polymorphe Konstruktion ohne das Ergebnis erreichen: Sie können eine Funktion schreiben, die Ihr Objekt anhand der erforderlichen Parameter erstellt.

3
Edouard A.

Wenn Leute so eine Frage stellen, denke ich mir gerne: "Was würde passieren, wenn dies tatsächlich möglich wäre?" Ich weiß nicht genau, was das bedeuten würde, aber ich denke, es hat etwas damit zu tun, dass man die Konstruktorimplementierung basierend auf dem dynamischen Typ des erstellten Objekts überschreiben kann.

Ich sehe hier eine Reihe von möglichen Problemen. Zum einen wird die abgeleitete Klasse zum Zeitpunkt des Aufrufs des virtuellen Konstruktors nicht vollständig erstellt. Daher gibt es potenzielle Probleme bei der Implementierung.

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

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

Schließlich kann, wie jemand anderes angedeutet hat, eine Art virtueller Konstruktor mit statischen Funktionen des Typs "create" oder "init" implementiert werden, die im Grunde dasselbe tun wie ein virtueller Konstruktor.

3

Der virtuelle Mechanismus funktioniert nur, wenn Sie über einen Klassenzeiger auf ein abgeleitetes Klassenobjekt verfügen. Die Konstruktion hat ihre eigenen Regeln für das Aufrufen von Basisklassenkonstruktoren, im Grunde genommen von der Basisklasse zur abgeleiteten Klasse. Wie kann ein virtueller Konstruktor nützlich sein oder aufgerufen werden? Ich weiß nicht, was andere Sprachen tun, aber ich kann nicht erkennen, wie ein virtueller Konstruktor nützlich oder sogar implementiert werden kann. Die Konstruktion muss für den virtuellen Mechanismus stattgefunden haben, um einen Sinn zu ergeben, und es muss auch eine Konstruktion geschehen, damit die vtable-Strukturen erstellt wurden, die die Mechanik des polymorphen Verhaltens bereitstellen.

2
Rich

Wir können es einfach nicht sagen. Wir können Konstruktoren nicht erben. Es gibt also keinen Sinn, sie als virtuell zu deklarieren, da der Virtuelle Polymorphismus bietet.

2
user3004790

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

#include<iostream.h>
using namespace std;
class aClass
{
    public:
        virtual aClass()
        {   
        }  
};
int main()
{
    aClass a; 
}

Dies verursacht einen Fehler. Dieser Code versucht, einen Konstruktor als virtuell zu deklarieren. Nun wollen wir versuchen zu verstehen, warum wir das virtuelle Schlüsselwort verwenden. Ein virtuelles Schlüsselwort wird verwendet, um Laufzeitpolymorphismus bereitzustellen. Versuchen Sie beispielsweise diesen Code.

#include<iostream.h>
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 main a=new anotherClass; reserviert ein Speicher für anotherClass in einem Zeiger a, der als Typ von aClass deklariert ist.Dies bewirkt, dass der Konstruktor (In aClass und anotherClass) automatisch aufgerufen wird es muss der Erzeugungskette folgen (dh zuerst der Basisklasse und dann der abgeleiteten Klassen) .. Wenn wir jedoch versuchen, einen delete a; zu löschen, führt dies dazu, dass nur der Basis-Destruktor aufgerufen wird. Daher müssen wir den Destruktor mit einem virtuellen Schlüsselwort behandeln. Virtueller Konstruktor ist also nicht möglich, aber virtueller Destruktor ist

Es gibt einen sehr grundlegenden Grund: Konstruktoren sind eigentlich statische Funktionen, und in C++ kann keine statische Funktion virtuell sein.

Wenn Sie viel Erfahrung mit C++ haben, wissen Sie alles über den Unterschied zwischen statischen und Member-Funktionen. Statische Funktionen sind mit dem CLASS-Objekt verbunden, nicht mit den Objekten (Instanzen). Daher wird kein "this" -Zeiger angezeigt. Nur Mitgliedsfunktionen können virtuell sein, da die vtable - die verborgene Tabelle der Funktionszeiger, die 'virtuelle' Arbeit macht - tatsächlich ein Datenmitglied jedes Objekts ist. 

Was macht nun der Konstrukteur? Es ist im Namen - ein "T" -Konstruktor initialisiert T-Objekte, sobald sie zugewiesen werden. Dies schließt automatisch aus, dass es eine Mitgliedsfunktion ist! Ein Objekt muss EXIST sein, bevor es einen "this" -Zeiger und somit eine vtable hat. Das bedeutet, dass selbst wenn die Sprache Konstruktoren als gewöhnliche Funktionen behandelte (aus verwandten Gründen, auf die ich mich nicht einlasse), sie statische Memberfunktionen sein müssten. 

Ein guter Weg, um dies zu sehen, ist das "Factory" -Muster, insbesondere die Factory-Funktionen. Sie tun, was Sie wollen, und Sie werden feststellen, dass Klasse T eine Werksmethode hat, die IMMER STATISCH ist. Es muss sein.

1
user3726672

Wenn Sie logisch darüber nachdenken, wie Konstruktoren funktionieren und welche Bedeutung/Verwendung eine virtuelle Funktion in C++ hat, werden Sie feststellen, dass ein virtueller Konstruktor in C++ ohne Bedeutung ist. Wenn Sie in C++ etwas Virtuelles deklarieren, bedeutet dies, dass es von einer Unterklasse der aktuellen Klasse überschrieben werden kann. Der Konstruktor wird jedoch aufgerufen, wenn das Objekt erstellt wird. Zu diesem Zeitpunkt können Sie keine Unterklasse der Klasse erstellen Erstellen der Klasse, sodass ein Konstruktor nie als virtuell deklariert werden muss.

Ein anderer Grund ist, dass die Konstruktoren denselben Namen wie der Klassenname haben. Wenn wir Konstruktor als virtuell deklarieren, sollte er in seiner abgeleiteten Klasse mit demselben Namen neu definiert werden. Sie können jedoch nicht den gleichen Namen von zwei Klassen haben. Daher ist es nicht möglich, einen virtuellen Konstruktor zu haben.

0
shobhit2905
  1. Wenn ein Konstruktor aufgerufen wird, obwohl bis zu diesem Zeitpunkt kein Objekt erstellt wurde, wissen wir immer noch, welche Art von Objekt erstellt werden soll, da der spezifische Konstruktor der Klasse, zu der das Objekt gehört, wurde bereits aufgerufen.

    Virtual Schlüsselwort, das einer Funktion zugeordnet ist, bedeutet, dass die Funktion eines bestimmten Objekttyps aufgerufen wird.

    Meiner Meinung nach ist es also nicht erforderlich, den virtuellen Konstruktor zu erstellen, da bereits der gewünschte Konstruktor aufgerufen wurde, dessen Objekt erstellt werden soll, und das virtuelle Erstellen des Konstruktors nur überflüssig ist, da Der objektspezifische Konstruktor wurde bereits aufgerufen und entspricht dem Aufruf der klassenspezifischen Funktion , die über ausgeführt wird das virtuelle Schlüsselwort.

    Obwohl die innere Implementierung den virtuellen Konstruktor aus vptr- und vtable-bezogenen Gründen nicht zulässt.


  1. Ein weiterer Grund ist, dass C++ eine statisch typisierte Sprache ist und wir den Typ einer Variablen zur Kompilierungszeit kennen müssen.

    Der Compiler muss den Klassentyp kennen, um das Objekt zu erstellen. Der Typ des zu erstellenden Objekts ist eine Entscheidung zur Kompilierungszeit.

    Wenn wir den Konstruktor virtuell machen, bedeutet dies, dass wir den Typ des Objekts während der Kompilierung nicht kennen müssen (dies ist die Funktion, die die virtuelle Funktion bereitstellt. Wir müssen das tatsächliche Objekt nicht kennen und brauchen nur den Basiszeiger, auf den es verweist Zeigen Sie auf ein tatsächliches Objekt. Rufen Sie die virtuellen Funktionen des angezeigten Objekts auf, ohne den Typ des Objekts zu kennen. Wenn Sie den Typ des Objekts zur Kompilierungszeit nicht kennen, ist dies mit den statisch typisierten Sprachen nicht vereinbar. Daher kann kein Laufzeit-Polymorphismus erzielt werden.

    Daher wird Constructor nicht aufgerufen, ohne den Typ des Objekts beim Kompilieren zu kennen. Und so scheitert die Idee, einen virtuellen Konstruktor zu erstellen.

0
Jos