Pourquoi n'avons-nous pas un constructeur virtuel en C++?
pourquoi C++ n'a-t-il pas de constructeur virtuel?
20 réponses
l'Entendre de la bouche des chevaux:).
D'après la FAQ de Bjarne Stroustrup sur le Style et la Technique C++ pourquoi n'avons-nous pas de constructeurs virtuels?
Un appel virtuel est un mécanisme pour obtenir le travail effectué partiels information. En particulier, "virtuel" nous permet d'appeler une fonction ne connaissant que les interfaces et non le type exact de l'objet. De créer un objet vous avez besoin d'informations complètes. Dans en particulier, vous besoin de connaître le type exact de ce que vous voulez créer. Conséquent, un "appel à un constructeur" ne peut pas être virtuel.
l'entrée FAQ continue pour donner le code pour un moyen d'atteindre cet objectif sans un constructeur virtuel.
les fonctions virtuelles fournissent essentiellement un comportement polymorphe. C'est-à-dire que lorsque vous travaillez avec un objet dont le type dynamique est différent du type statique (temps de compilation) auquel il est fait référence, il fournit un comportement qui est approprié pour le type actuel d'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 la même que le type d'objet réel depuis:
pour construire un objet, un constructeur a besoin du type exact de l'objet qu'il doit créer [...] Outre.[ ..]vous ne pouvez pas avoir un pointeur vers un constructeur
(Bjarne Stroustup (P424 Le Langage de Programmation C++ SE))
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 abstract factory pattern , car vous pouvez passer l'objet représentant la classe autour au lieu de faire votre propre), C++ est un langage basé sur la classe, et n'a pas d'objets représentant l'une des constructions du langage. La classe n'existe pas en tant qu'objet à l'exécution, donc vous ne pouvez pas appel d'une méthode virtuelle.
cela correspond à la philosophie "vous ne payez pas pour ce que vous n'utilisez pas", bien que chaque grand projet C++ que j'ai vu ait fini par mettre en œuvre une forme d'usine abstraite ou de réflexion.
deux raisons auxquelles je peux penser:
raison technique
l'objet n'existe qu'après la fin du constructeur.Pour que le constructeur soit envoyé en utilisant la table virtuelle , il doit y avoir un objet existant avec un pointeur vers la table virtuelle, mais comment un pointeur vers la table virtuelle peut-il exister si l'objet n'existe toujours pas? :)
logique raison
vous utilisez le mot-clé virtuel lorsque vous souhaitez déclarer un peu polymorphes comportement. Mais il n'y a rien de polymorphe avec les constructeurs , le travail des constructeurs en C++ est simplement de mettre des données objet sur la mémoire . Étant donné que les tables virtuelles (et le polymorphisme en général) portent toutes sur le comportement polymorphe plutôt que sur les données polymorphiques , il n'y a aucun sens à déclarer un constructeur virtuel.
Nous faisons, c'est juste 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;
}
pour des raisons sémantiques mises à part, il n'y a pas de vtable avant la construction de l'objet, ce qui rend une désignation virtuelle inutile.
résumé : la norme C++ pourrait spécifier une notation et un comportement pour "le constructeur virtuel"s qui est raisonnablement intuitif et pas trop difficile à prendre en charge par les compilateurs, mais pourquoi faire un changement de norme pour cela spécifiquement lorsque la fonctionnalité peut déjà être proprement mis en œuvre en utilisant create()
/ clone()
(voir ci-dessous)? Ce n'est pas aussi utile que beaucoup d'autres proposition linguistique en préparation.
Discussion
postulons un mécanisme de "constructeur virtuel":
Base* p = new Derived(...);
Base* p2 = new p->Base(); // possible syntax???
ci-dessus, la première ligne construit un Derived
objet *p
virtuelle expédition table peut raisonnablement fournir un "virtuel constructeur" pour une utilisation dans la deuxième ligne. (Des douzaines de réponses sur cette page indiquant "l'objet n'existe pas encore donc la construction virtuelle est impossible " sont inutilement myopiquement axés sur l'objet à construire.)
la deuxième ligne postule la notation new p->Base()
pour demander l'attribution dynamique et la construction par défaut d'un autre objet Derived
.
Notes:
-
le compilateur doit orchestrer l'allocation de mémoire avant d'appeler le constructeur - les constructeurs supportent normalement l'allocation automatique (officieusement" stack"), statique (pour la portée globale / namespace et la classe - / fonction -
static
objets), et dynamique (informellement" heap") lorsquenew
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 allocation dynamique est la seule approche qui a du sens- il est possible d'allouer à l'exécution, les quantités indiquées de la mémoire sur la pile - par exemple, GCC est de longueur variable tableau extension ,
alloca()
-, mais conduit à d'importantes inefficacités et de la complexité (par exemple, ici et ici , respectivement)
- il est possible d'allouer à l'exécution, les quantités indiquées de la mémoire sur la pile - par exemple, GCC est de longueur variable tableau extension ,
-
-
pour l'allocation dynamique il doit retourner un pointeur pour que la mémoire puisse être
delete
d plus tard. -
la notation postulée mentionne explicitement
new
pour mettre l'accent sur l'allocation dynamique et le type de résultat du pointeur.
le compilateur devrait:
- découvrez combien de mémoire
Derived
nécessaire, soit par appeler une fonction implicitevirtual
sizeof
ou avoir de telles informations disponibles via RTTI - appel
operator new(size_t)
pour allouer de la mémoire - invoquer
Derived()
placementnew
.
OU
- créer une entrée vtable supplémentaire pour une fonction qui combine l'allocation dynamique et la construction
il ne semble pas insurmontable pour spécifier et implémenter des constructeurs virtuels, mais la question à un million de dollars est: comment serait-ce mieux que ce qui est possible en utilisant les fonctionnalités C++ existantes...? Personnellement, Je ne vois aucun avantage sur la solution ci-dessous.
`clone () `et` create () '
Le C++ FAQ documents "virtuel constructeur" idiome , contenant virtual
create()
et clone()
méthodes par défaut-construire ou copier-construire un nouvel objet attribué dynamiquement:
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 des arguments, bien que pour correspondre à la signature de la fonction virtual
de la classe de base / interface, les arguments pour outrepasser doivent correspondre exactement à l'une des surcharges de la classe de base. Avec ces installations explicitement fournies par l'utilisateur, il est facile d'ajouter la journalisation, instrumentation, modification de l'attribution de la mémoire, etc..
bien que le concept de constructeur virtuel ne Cadre pas bien puisque le type d'objet est une condition préalable à la création d'un objet, il n'est pas complètement dépassé.
le modèle de conception de la "méthode d'usine" de GOF utilise le "concept" de constructeur virtuel, qui est manuel dans certaines situations de conception.
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ée". Il est appelé qu'une seule fois lorsqu'un objet est déclaré. Ainsi, un constructeur ne peut pas être rendu virtuel en C++.
fonctions Virtuelles en C++ sont une mise en application du polymorphisme de l'exécution, et ils feront de la fonction prépondérante. En général, le mot-clé virtual
est utilisé en C++ lorsque vous avez besoin de comportement dynamique. Il ne fonctionne que lorsque l'objet existe. Alors que les constructeurs sont utilisés pour créer des objets. Constructeurs sera appelée au moment de la création de l'objet.
donc si vous créez le constructeur comme virtual
, selon la définition de mot-clé virtuel, il devrait avoir objet existant à utiliser, mais le constructeur est utilisé pour créer l'objet, de sorte que ce cas n'existe pas. Donc, vous ne devriez pas utiliser le constructeur virtuel.
donc, si nous essayons de déclarer le compilateur de constructeur virtuel jeter une erreur:
les constructeurs ne peuvent pas être déclarés virtuels
vous pouvez trouver un exemple et la raison technique pour laquelle il n'est pas permis dans la réponse de @stefan. Maintenant, une réponse logique à cette question selon moi est:
l'utilisation principale du mot-clé virtuel est de permettre un comportement polymorphe lorsque nous ne savons pas quel type d'objet le pointeur de classe de base pointera.
mais pensez à ceci est une façon plus primitive, pour l'utilisation de la fonctionnalité virtuelle, vous aurez besoin d'un pointeur. Et ce n'est un pointeur exiger? Un objet à point de! (en considérant le cas pour l'exécution correcte du programme)
ainsi, nous avons essentiellement besoin d'un objet qui existe déjà quelque part dans la mémoire (nous ne sommes pas concernés par la façon dont la mémoire a été attribuée, il peut être au moment de la compilation ou de l'exécution) de sorte que notre pointeur puisse correctement pointer vers cet objet.
maintenant, pensez à la situation du moment où l'objet de la classe à pointer est assigné à un certain souvenir -> Son constructeur sera appelé automatiquement à cette instance!
donc nous pouvons voir que nous n'avons pas réellement besoin de nous inquiéter du fait que le constructeur soit virtuel, parce que dans tous les cas vous souhaitez utiliser un comportement polymorphe notre constructeur aurait déjà été exécuté rendant notre objet prêt pour l'utilisation!
vous ne devriez pas appeler la fonction virtuelle dans votre constructeur non plus. 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 elle: vous pouvez écrire une fonction qui construira votre objet selon les paramètres nécessaires.
Quand les gens posent une question comme ça, j'aime à penser à moi-même "qu'arriverait-il si c'était réellement possible?"Je ne sais pas vraiment ce que cela signifierait, mais je suppose que cela aurait quelque chose à voir avec le fait de pouvoir outrepasser l'implémentation du constructeur basée sur le type dynamique de l'objet créé.
je vois un certain nombre de problèmes potentiels avec cette. D'une part, la classe dérivée ne sera pas entièrement construite au moment où le virtuel le constructeur est appelé, donc il y a des problèmes potentiels avec la mise en œuvre.
deuxièmement, que se passerait-il en cas d'héritage multiple? Votre constructeur virtuel serait appelé plusieurs fois probablement, vous devez avoir un moyen de savoir qui a appelé.
Troisièmement, en général au moment de la construction, l'objet n'a pas la table virtuelle entièrement construit, cela signifie qu'il faut une grande modification de la spécification linguistique pour tenir compte du fait que le type dynamique de l'objet serait connu au moment de la construction. Cela permettrait alors au constructeur de classe de base d'appeler peut-être d'autres fonctions virtuelles au moment de la construction, avec un type de classe dynamique pas entièrement construit.
enfin, comme quelqu'un d'autre l'a souligné, vous pouvez implémenter une sorte de constructeur virtuel en utilisant des fonctions statiques de type "create" ou "init" qui font essentiellement la même chose qu'un virtuel constructeur ferait.
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 des constructeurs de classe de base, essentiellement de classe de base à dérivé. Comment un constructeur virtuel est utile ou appelé? Je ne sais pas ce que font les autres langues, mais je ne vois pas comment un constructeur virtuel pourrait être utile ou même implémenté. La Construction doit avoir eu lieu pour que le mécanisme virtuel ait un sens et la construction aussi il faut que les structures vtable aient été créées pour fournir la mécanique du comportement polymorphe.
on ne peut pas simplement le dire comme ça.. Nous ne pouvons pas hériter des constructeurs. Il est donc inutile de les déclarer virtuels, car le virtuel offre du polymorphisme .
une table virtuelle(vtable) est créée pour chaque classe ayant une ou plusieurs "fonctions virtuelles". Chaque fois qu'un objet est créé d'une telle classe, il contient un "pointeur virtuel" qui pointe vers la base du vtable correspondant. Chaque fois qu'il y a un appel de fonction virtuel, le vtable est utilisé pour résoudre à l'adresse de fonction. Le constructeur ne peut pas être virtuel, parce que lorsque le constructeur d'une classe est exécuté il n'y a pas de vtable dans la mémoire, ce qui signifie qu'aucun pointeur virtuel n'est encore défini. D'où l' le constructeur doit toujours être non-virtuel.
virtuelle C++ constructeur n'est pas possible.Par exemple, vous ne pouvez pas marquer un constructeur virtuel.Essayez ce code
#include<iostream.h>
using namespace std;
class aClass
{
public:
virtual aClass()
{
}
};
int main()
{
aClass a;
}
Il provoque une erreur.Ce code essaie de déclarer un constructeur comme virtuel. Essayons maintenant de comprendre pourquoi nous utilisons le mot-clé virtuel. Le mot-clé virtuel est utilisé pour fournir le polymorphisme du temps d'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();
}
En main a=new anotherClass;
alloue de la mémoire pour les anotherClass
dans un pointeur a
déclaré comme type de aClass
.Cela fait que le constructeur (dans aClass
et anotherClass
) appelle automatiquement.Nous n'avons donc pas besoin de marquer le constructeur comme étant virtuel.Parce que lorsqu'un objet est créé, il doit suivre la chaîne de création (I. e d'abord la base et ensuite les classes dérivées).
Mais quand nous essayons de supprimer un delete a;
il provoque à appeler seulement le destructeur de base.Donc nous devons gérer le destructeur en utilisant le mot-clé virtuel. donc le constructeur virtuel n'est pas possible mais destructeur virtuel est .Merci
il y a une raison très simple: les constructeurs sont effectivement 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 statiques et les fonctions de membre. Les fonctions statiques sont associées à la classe, pas aux objets (instances), donc ils ne voient pas de pointeur "ceci". Seules les fonctions de membre peuvent être virtuelles, parce que le vtable-la table cachée de pointeurs de fonction qui fait "virtuel" de travail est vraiment un membre de données de chaque objet.
maintenant, quel est le travail du constructeur? Il est dans le nom-un constructeur" T " initialise les objets T au fur et à mesure qu'ils sont alloués. Cela empêche automatiquement qu'il soit une fonction de membre! Un objet doit exister avant d'avoir un pointeur "ceci" et donc un vtable. 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 que je n'entrerai pas), ils devraient être membres statiques. fonction.
une bonne façon de voir cela est de regarder le modèle" usine", en particulier les fonctions d'usine. Ils font ce que vous voulez, et vous remarquerez que si la classe T a une méthode d'usine, elle est toujours statique. Il doit être.
si vous pensez logiquement au fonctionnement des constructeurs et à la signification/utilisation d'une fonction virtuelle en C++, alors vous réaliserez qu'un constructeur virtuel serait dénué de sens en C++. Déclarer quelque chose de virtuel en C++ signifie qu'il peut être dépassé par une sous-classe de la classe courante, cependant le constructeur est appelé quand l'objecté est créé, à ce moment vous ne pouvez pas créer une sous-classe de la classe, vous devez créer la classe donc il ne serait jamais nécessaire de déclarer un constructeur virtuel.
et une autre raison est, les constructeurs ont le même nom que son nom de classe et si nous déclarons constructeur comme virtuel, alors il devrait être redéfini dans sa classe dérivée avec le même nom, mais vous ne pouvez pas avoir le même nom de deux classes. Donc, il n'est pas possible d'avoir un constructeur virtuel.
le Vpointer est créé au moment de la création de l'objet. vpointer n'existe pas avant la création d'un objet. il n'y a donc pas de raison de rendre le constructeur virtuel.