web-dev-qa-db-fra.com

Pourquoi n'avons-nous pas de constructeur virtuel en C++?

Pourquoi C++ n'a-t-il pas de constructeur virtuel?

220
Arjun

Écoutez-le de la bouche du cheval :). 

Tiré de la FAQ sur le style et la technique C++ de Bjarne Stroustrup Pourquoi n'avons-nous pas de constructeurs virtuels?

Un appel virtuel est un mécanisme permettant d’effectuer un travail partiel information. En particulier, "virtuel" nous permet d'appeler une fonction ne connaissant que les interfaces et non le type exact de l'objet. À créer un objet dont vous avez besoin d'informations complètes. En particulier, vous besoin de connaître le type exact de ce que vous voulez créer. Par conséquent, un "appel à un constructeur" ne peut pas être virtuel.

L'entrée FAQ donne ensuite le code permettant de parvenir à cette fin sans constructeur virtuel.

212
aJ.

Les fonctions virtuelles fournissent essentiellement un comportement polymorphe. Autrement dit, lorsque vous travaillez avec un objet dont le type dynamique est différent du type statique (à la compilation) auquel il est fait référence, il fournit un comportement approprié pour le type actual - de l'objet au lieu du type statique. de l'objet.

Maintenant, essayez d'appliquer ce genre de comportement à un constructeur. Lorsque vous construisez un objet, le type statique est toujours identique au type d'objet réel puisque:

Pour construire un objet, un constructeur a besoin du type exact d'objet à créer [...] De plus, vous ne pouvez pas [...] avoir un pointeur sur un constructeur

(Bjarne Stroustup (P424, le langage de programmation C++ SE))

119
Anton Gogolev

Contrairement aux langages orientés objet tels que Smalltalk ou Python, où le constructeur est une méthode virtuelle de l'objet représentant la classe (ce qui signifie que vous n'avez pas besoin du GoF modèle de fabrique abstraite , car vous pouvez transmettre l'objet représentant la classe. C++ est un langage basé sur les classes et ne contient pas d’objets représentant les constructions du langage. La classe n'existe pas en tant qu'objet au moment de l'exécution, vous ne pouvez donc pas appeler de méthode virtuelle dessus.

Cela correspond à la philosophie du "vous ne payez pas pour ce que vous n'utilisez pas", bien que tous les grands projets C++ que j'ai vus aient fini par implémenter une sorte de fabrique abstraite ou de réflexion.

57
Pete Kirkham

je peux penser à deux raisons:

Raison technique

L'objet n'existe qu'après la fin du constructeur. Pour que le constructeur soit distribué à l'aide de la table virtuelle, il doit exister un objet existant avec un pointeur sur la table virtuelle, mais comment peut-il exister un pointeur sur la table virtuelle si l'objet n'existe toujours pas? :)

Raison logique

Vous utilisez le mot clé virtual lorsque vous souhaitez déclarer un comportement quelque peu polymorphe. Mais il n'y a rien de polymorphe avec les constructeurs, le travail des constructeurs en C++ est de simplement mettre une donnée d'objet sur la mémoire. Etant donné que les tables virtuelles (et le polymorphisme en général) concernent essentiellement le comportement polymorphique plutôt que les données polymorphes, il est inutile de déclarer un constructeur virtuel.

39
user88637

Mis à part les raisons sémantiques, il n'y a pas de table virtuelle avant la construction de l'objet, rendant ainsi une désignation virtuelle inutile.

13
Marius

Nous faisons, ce n'est tout simplement pas un constructeur :-)

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

Résumé: le standard C++ pourrait spécifier une notation et un comportement pour les "constructeurs virtuels" raisonnablement intuitifs et faciles à supporter par les compilateurs, mais pourquoi effectuer une modification standard spécifiquement à cet effet quand la fonctionnalité peut déjà être implémentée proprement à l'aide de create()clone() (voir ci-dessous)? Ce n'est pas aussi utile que de nombreuses autres propositions linguistiques en préparation.

Discussion

Posons un mécanisme de "constructeur virtuel":

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

Dans ce qui précède, la première ligne construit un objet Derived, de sorte que la table de répartition virtuelle de *p peut raisonnablement fournir un "constructeur virtuel" à utiliser dans la deuxième ligne. (Des dizaines de réponses sur cette page indiquent "l'objet n'existe pas encore, alors la construction virtuelle est impossible" se concentre inutilement de manière myopique sur l'objet à construire.)

La deuxième ligne postule la notation new p->Base() pour demander une allocation dynamique et la construction par défaut d'un autre objet Derived.

Remarques:

  • _/le compilateur doit orchestrer l'allocation de mémoire avant d'appeler le constructeur - les constructeurs prennent normalement en charge l'allocation automatique (informellement "stack"), static (pour global/namespace scope et class-/function -static objets) et dynamic (informellement "heap") lorsque new est utilisé

    • la taille de l'objet à construire par p->Base() ne peut généralement pas être connue au moment de la compilation, donc l'allocation dynamique est la seule approche logique

      • il est possible d'allouer des quantités de mémoire spécifiées à l'exécution à la pile - par ex. Extension de tableau de longueur variable de GCC } _, alloca() - mais conduit à des inefficacités et des complexités importantes (par exemple, ici et ici respectivement)
  • pour l'allocation dynamique, doit renvoyer un pointeur afin que la mémoire puisse être deleted plus tard.

  • la notation postulée énumère explicitement new pour mettre en évidence l'allocation dynamique et le type de résultat du pointeur.

Le compilateur devrait:

  • savoir combien de mémoire Derived est nécessaire, en appelant une fonction implicite virtualsizeof ou en disposant de telles informations via RTTI
  • appelez operator new(size_t) pour allouer de la mémoire
  • invoquer Derived() avec placement new.

OR

  • créer une entrée vtable supplémentaire pour une fonction combinant allocation dynamique et construction

Donc, il ne semble pas insurmontable de spécifier et d'implémenter des constructeurs virtuels, mais la question à un million de dollars est la suivante: en quoi serait-il meilleur que ce qui est possible en utilisant les fonctionnalités de langage C++ existantes ...? Personnellement, je ne vois aucun avantage par rapport à la solution ci-dessous.


`clone ()` et `create ()`

La C++ FAQ documente un idiome de "constructeur virtuel" , contenant les méthodes virtualcreate() et clone() pour construire par défaut ou copier-construire un nouvel objet alloué de manière dynamique:

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(); }

Il est également possible de modifier ou de surcharger create() pour accepter les arguments, bien que pour correspondre à la signature de la fonction virtual de la classe/interface de base, les arguments à remplacer doivent correspondre exactement à l'un des surcharges de la classe de base. Avec ces fonctionnalités explicites fournies par l'utilisateur, il est facile d'ajouter une journalisation, une instrumentation, une modification de l'allocation de mémoire, etc.

11
Tony Delroy

Bien que le concept de constructeur virtuel ne s'intègre pas bien dans la mesure où le type d'objet est une condition préalable à la création d'objet, il n'est pas complètement annulé.

Le modèle de conception "méthode d'usine" de GOF utilise le "concept" de constructeur virtuel, qui est pratique dans certaines situations de conception.

5
Shraddha Sinha

Les fonctions virtuelles en C++ sont une implémentation du polymorphisme au moment de l’exécution, et elles remplacent les fonctions. Généralement, le mot clé virtual est utilisé en C++ lorsque vous avez besoin d'un comportement dynamique. Cela fonctionnera seulement quand l'objet existe. Alors que les constructeurs sont utilisés pour créer les objets. Les constructeurs seront appelés au moment de la création de l'objet.

Ainsi, si vous créez le constructeur en tant que virtual, conformément à la définition du mot clé virtuel, l'objet existant doit être utilisé, mais le constructeur est utilisé pour créer l'objet. Ce cas n'existe donc jamais. Donc, vous ne devriez pas utiliser le constructeur en tant que virtuel.

Donc, si nous essayons de déclarer le compilateur de constructeur virtuel jette une erreur: 

Les constructeurs ne peuvent pas être déclarés virtuels

5
Neha kumari

Les fonctions virtuelles sont utilisées pour invoquer des fonctions basées sur le type d'objet pointé par le pointeur et non sur le type de pointeur lui-même. Mais un constructeur n'est pas "invoqué". Il n'est appelé qu'une fois lorsqu'un objet est déclaré. Ainsi, un constructeur ne peut pas être rendu virtuel en C++.

4
skrtbhtngr

Vous pouvez trouver un exemple et la raison technique pour laquelle cela n'est pas autorisé dans la réponse de @stefan. Maintenant, une réponse logique à cette question selon moi est la suivante:

L'utilisation principale du mot-clé virtual est d'activer le comportement polymorphe lorsque nous ne savons pas quel type d'objet sera pointé par le pointeur de la classe de base.

Mais pensez à cela de manière plus primitive, pour utiliser la fonctionnalité virtuelle, vous aurez besoin d’un pointeur. Et que nécessite un pointeur? Un objet à pointer! (en considérant le cas pour l'exécution correcte du programme)

Donc, nous avons essentiellement besoin d’un objet qui existe déjà quelque part dans la mémoire (nous ne nous intéressons pas à la façon dont la mémoire a été allouée, cela peut être au moment de la compilation ou de l’exécution) pour que notre pointeur puisse correctement pointer sur cet objet.

Maintenant, pensons à la situation concernant le moment où l'objet de la classe à indiquer se voit attribuer un peu de mémoire -> Son constructeur sera appelé automatiquement à cette instance elle-même!

Ainsi, nous pouvons voir que nous n'avons pas réellement besoin de nous inquiéter du fait que le constructeur soit virtuel car, dans tous les cas, vous souhaitez utiliser un comportement polymorphe, notre constructeur aurait déjà été exécuté, rendant ainsi notre objet prêt à être utilisé!

4
Kushan Mehta

Une table virtuelle (vtable) est créée pour chaque classe comportant une ou plusieurs «fonctions virtuelles». Chaque fois qu'un objet de cette classe est créé, il contient un "pointeur virtuel" qui pointe vers la base de la table vtable correspondante. Chaque fois qu'il y a un appel de fonction virtuelle, vtable est utilisé pour résoudre l'adresse de la fonction . Le constructeur ne peut pas être virtuel, car lorsque le constructeur d'une classe est exécuté, il n'y a pas de table virtuelle dans la mémoire, ce qui signifie qu'aucun pointeur virtuel n'est encore défini. Par conséquent, le constructeur doit toujours être non virtuel.

3
Navneet Agarwal

Vous ne devriez pas non plus appeler une fonction virtuelle dans votre constructeur. Voir: http://www.artima.com/cppsource/nevercall.html

De plus, je ne suis pas sûr que vous ayez vraiment besoin d'un constructeur virtuel. Vous pouvez réaliser une construction polymorphe sans cela: vous pouvez écrire une fonction qui construira votre objet en fonction des paramètres nécessaires.

3
Edouard A.

Quand les gens posent une question comme celle-ci, j'aime bien penser à moi-même: "que se passerait-il si c'était réellement possible?" Je ne sais pas vraiment ce que cela signifierait, mais j'imagine que cela aurait quelque chose à voir avec le fait de pouvoir remplacer l'implémentation du constructeur en fonction du type dynamique de l'objet en cours de création.

Je vois un certain nombre de problèmes potentiels avec cela. D'une part, la classe dérivée ne sera pas entièrement construite au moment de l'appel du constructeur virtuel, de sorte qu'il existe des problèmes potentiels avec l'implémentation.

Deuxièmement, que se passerait-il en cas de succession multiple? Votre constructeur virtuel serait appelé plusieurs fois sans doute, vous auriez alors besoin d'un moyen de savoir quel nom était appelé.

Troisièmement, de manière générale au moment de la construction, la table virtuelle n’a pas été entièrement construite sur l’objet. Cela implique donc une modification importante de la spécification du langage pour tenir compte du fait que le type dynamique de l’objet serait connu lors de la construction temps. Cela permettrait alors au constructeur de la classe de base d'appeler éventuellement d'autres fonctions virtuelles au moment de la construction, avec un type de classe dynamique non entièrement construit.

Enfin, comme quelqu'un l'a fait remarquer, vous pouvez implémenter un type de constructeur virtuel en utilisant des fonctions statiques de type "create" ou "init" qui font essentiellement la même chose qu'un constructeur virtuel.

3
1800 INFORMATION

Le mécanisme virtuel ne fonctionne que lorsque vous avez un pointeur de classe basé sur un objet de classe dérivé. Construction a ses propres règles pour l'appel de constructeurs de classe de base, essentiellement une classe de base à dérivée. Comment un constructeur virtuel peut-il être utile ou appelé? Je ne sais pas ce que font les autres langages, mais je ne vois pas comment un constructeur virtuel pourrait être utile ou même implémenté. Pour que le mécanisme virtuel ait un sens, il faut que la construction ait eu lieu et pour que les structures vtable aient été créées, elles fournissent la mécanique du comportement polymorphe.

2
Rich

Ne pouvons-nous pas simplement le dire comme .. Nous ne pouvons pas hériter des constructeurs. Il est donc inutile de les déclarer virtuels car le virtuel fournit un polymorphisme.

2
user3004790

Le constructeur virtuel C++ n'est pas possible.Par exemple, vous ne pouvez pas marquer un constructeur comme virtuel.Essayez ce code

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

Cela provoque une erreur.Ce code essaie de déclarer un constructeur en tant que virtual . Essayons maintenant de comprendre pourquoi nous utilisons un mot clé virtuel. Le mot clé virtuel est utilisé pour fournir un polymorphisme à l'exécution. Par exemple, essayez ce 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(); 
}

Dans a=new anotherClass; principal alloue une mémoire pour anotherClass dans un pointeur a déclaré en tant que type de aClass. Cela oblige le constructeur (dans aClass et anotherClass) à appeler automatiquement. Il n'est donc pas nécessaire de marquer le constructeur en tant que virtual.Because lorsqu'un objet est créé il doit suivre la chaîne de création (c'est-à-dire d'abord la base puis les classes dérivées) . Mais lorsque nous essayons de supprimer un delete a;, il n'appelle que le destructeur de base.Nous devons donc gérer le destructeur avec un mot clé virtuel. Le constructeur virtuel n'est donc pas possible, mais le destructeur virtuel est . Merci

1

Il y a une raison de base: les constructeurs sont en réalité des fonctions statiques et, en C++, aucune fonction statique ne peut être virtuelle.

Si vous avez beaucoup d'expérience avec C++, vous savez tout sur la différence entre les fonctions static et member. Les fonctions statiques sont associées à la classe, pas aux objets (instances), elles ne voient donc pas le pointeur "ceci". Seules les fonctions membres peuvent être virtuelles, car la table vtable - la table masquée des pointeurs de fonction qui rend le travail 'virtuel' - est en réalité un membre de données de chaque objet. 

Maintenant, quel est le travail du constructeur? C'est dans le nom qu'un constructeur "T" initialise les objets T comme ils sont alloués. Cela exclut automatiquement qu'il s'agisse d'une fonction membre! Un objet doit EXISTER avant d'avoir un pointeur "this" et donc une table. Cela signifie que même si le langage traitait les constructeurs comme des fonctions ordinaires (ce n'est pas le cas, pour des raisons connexes, je ne les aborderai pas), ils devraient être des fonctions membres statiques. 

Une excellente façon de voir cela consiste à examiner le modèle "Factory", en particulier les fonctions d'usine. Ils font ce que vous recherchez, et vous remarquerez que si la classe T a une méthode factory, elle est TOUJOURS STATIQUE. Ça doit être.

1
user3726672

Si vous réfléchissez logiquement au fonctionnement des constructeurs et à la signification/utilisation d'une fonction virtuelle en C++, vous réaliserez qu'un constructeur virtuel n'aurait aucun sens en C++. Déclarer quelque chose de virtuel en C++ signifie qu’elle peut être remplacée par une sous-classe de la classe en cours. Cependant, le constructeur est appelé lors de la création de l’objet faisant l'objet de la objection. créer la classe de sorte qu'il ne serait jamais nécessaire de déclarer un constructeur virtuel.

Et une autre raison est que les constructeurs ont le même nom que son nom de classe et si nous déclarons le constructeur comme virtuel, il devrait alors être redéfini dans sa classe dérivée avec le même nom, mais vous ne pouvez pas avoir le même nom pour deux classes. Il n'est donc pas possible d'avoir un constructeur virtuel.

0
shobhit2905
  1. Lorsqu'un constructeur est appelé, bien qu'aucun objet n'ait été créé à ce stade, nous savons toujours quel type d'objet va être créé car le constructeur spécifique de la classe à laquelle l'objet appartient a déjà été appelé.

    Virtual le mot-clé associé à une fonction signifie que la fonction d'un type d'objet particulier va être appelé.

    Donc, ma pensée dit qu’il n’est pas nécessaire de créer le constructeur virtuel, car le constructeur souhaité dont l’objet va être créé a déjà été appelé et la création d’un constructeur virtuel n’est qu’une tâche redondante, car le constructeur spécifique à l’objet a déjà été invoqué, ce qui revient à appeler fonction spécifique à la classe, obtenue par le mot-clé virtual.

    Bien que l’implémentation interne n’autorise pas les constructeurs virtuels pour les raisons liées à vptr et vtable.


  1. Une autre raison est que C++ est un langage à typage statique et que nous avons besoin de connaître le type d'une variable au moment de la compilation.

    Le compilateur doit connaître le type de classe pour créer l'objet. Le type d'objet à créer est une décision à la compilation.

    Si nous rendons le constructeur virtuel, cela signifie que nous n’avons pas besoin de connaître le type de l’objet au moment de la compilation (c’est ce que la fonction virtuelle fournit. Nous n’avons pas besoin de connaître l’objet lui-même et nous avons simplement besoin du pointeur de base pour pointez un objet réel, appelez les fonctions virtuelles de l'objet pointé sans connaître le type de l'objet) et si nous ne connaissons pas le type de l'objet au moment de la compilation, il est opposé aux langages à typage statique. Et par conséquent, le polymorphisme à l'exécution ne peut pas être atteint.

    Constructor ne sera donc pas appelé sans connaître le type de l’objet au moment de la compilation. Et donc l'idée de faire un constructeur virtuel échoue.

0
Jos