L'appel de méthode virtuelle dans la classe de base constructeur
Je sais que l'appel d'une méthode virtuelle à partir d'un constructeur de classe de base peut être dangereux depuis la classe fille peut ne pas être dans un état valide. (au moins en C#)
Ma question est ce que si la méthode virtuelle est celui qui initialise l'état de l'objet ? Est il bon de le faire ou faut-il un processus en deux étapes, d'abord à créer l'objet et ensuite à la charge de l'état ?
Première option: (en utilisant le constructeur pour initialiser l'état)
public class BaseObject {
public BaseObject(XElement definition) {
this.LoadState(definition);
}
protected abstract LoadState(XElement definition);
}
Deuxième option: (à l'aide d'un processus en deux étapes)
public class BaseObject {
public void LoadState(XElement definition) {
this.LoadStateCore(definition);
}
protected abstract LoadStateCore(XElement definition);
}
Dans la première méthode, le consommateur, le code permet de créer et d'initialiser l'objet avec une seule instruction:
//The base class will call the virtual method to load the state.
ChildObject o = new ChildObject(definition)
Dans la deuxième méthode, le consommateur devra créer l'objet et à la charge de l'état:
ChildObject o = new ChildObject();
o.LoadState(definition);
- Pourquoi est-ce dangereux en C#? Vous écrivez que la classe enfant peut ne pas être dans un état valide. Tu veux dire que la classe enfant peut ne pas avoir été initialisé encore?
- L'enfant de la classe n'EST pas encore initialisé. Classe de Base des constructeurs exécuter avant de enfant de constructeurs de classe. Si l'enfant constructeurs de classe n'est pas anodin, l'état de l'objet sera incohérent.
- Il n'y a rien de mal avec l'appel de fonction VIRTUELLE dans le constructeur. Plutôt, Ce que vous entendez doit être fonction ABSTRAITE, non? Donc, votre question semble défectueux.
- Je ne vois pas où est la différence entre virtuel et de méthodes abstraites. Dans les deux cas, elle permet au constructeur de base pour utiliser l'objet enfant dans un état non valide.
- J'ai écrit au sujet de ce problème ici, avec des captures d'écran et code de.
Vous devez vous connecter pour publier un commentaire.
(Cette réponse s'applique à C# et Java. Je crois que C++ fonctionne différemment sur ce sujet).
L'appel d'une méthode virtuelle dans un constructeur est en effet dangereux, mais parfois, il peut se retrouver avec le plus propre code.
Je voudrais essayer d'éviter, lorsque cela est possible, mais sans courber le design très. (Par exemple, le "initialiser plus tard" option interdit l'immuabilité.) Si vous ne l'utilisation d'une méthode virtuelle dans le constructeur, document, il très fortement. Tant que tout le monde est conscient de ce qu'il fait, il ne devrait pas provoquer de trop de nombreux problèmes. Je voudrais essayer de limiter la visibilité si, comme vous l'avez fait dans votre premier exemple.
EDIT: Une chose qui est important ici, c'est qu'il y a une différence entre C# et Java, dans l'ordre de l'initialisation. Si vous avez une classe telle que:
où la
Parent
constructeur appelleShowFoo
, en C#, il affiche 10. Le programme équivalent en Java devrait afficher 0.En C++, l'appel d'une méthode virtuelle dans une classe de base constructeur de simplement appeler la méthode comme si la classe dérivée n'existe pas encore (car il n'a pas). Cela signifie donc que l'appel est résolu au moment de la compilation pour quelle que soit la méthode, il doit appeler dans la classe de base (ou les classes dérivées à partir de).
Testé avec GCC, il vous permet d'appeler une fonction virtuelle pure à partir d'un constructeur, mais il donne un avertissement, et les résultats dans un lien à l'erreur. Il semble que ce comportement n'est pas défini par la norme:
"Fonctions de membre peut être appelé à partir d'un constructeur (ou destructeur) d'une classe abstraite; l'effet de faire un appel virtuel (classe.virtuel) à une fonction virtuelle pure directement ou indirectement à l'objet en cours de création (ou détruit) à partir d'un tel constructeur (ou destructeur) n'est pas défini."
Avec C++ les méthodes virtuelles sont acheminés à travers la vtable pour la classe qui est en cours de construction. Donc dans votre exemple, il serait de générer une méthode virtuelle pure exception puisque tout BaseObject est en cours de construction il n'y a pas LoadStateCore méthode à invoquer.
Si la fonction n'est pas abstrait, mais ne font rien, alors vous aurez souvent le programmeur se gratter la tête en essayant de se rappeler pourquoi il est que la fonction n'est pas appelée.
Pour cette raison que vous simplement ne pouvez pas le faire de cette façon en C++ ...
Pour le C++, le constructeur de base est appelée avant que la dérivée constructeur, ce qui signifie que la table virtuelle qui contient les adresses de la classe dérivée est substituée fonctions virtuelles) n'existe pas encore. Pour cette raison, il est considéré comme une chose TRÈS dangereuse à faire (surtout si les fonctions sont virtuelle pure dans la classe de base...ce sera la cause d'une pure virtuel exception).
Il y a deux façons de contourner cela:
Un exemple de (1) est:
Un exemple de (2) est:
Approche (2) utilise le modèle de Fabrique de la mise en œuvre de la
accessible
classe (qui fourniront la même interface que vosbase
classe). L'un des principaux avantages est que vous obtenez de l'initialisation lors de la construction deaccessible
qui est en mesure de faire usage de membres virtuels deaccessible_impl
en toute sécurité.Pour le C++, l'article 12.7, paragraphe 3 de la Norme couvre ce cas.
Pour résumer, c'est légal. Il permettra de résoudre à la bonne fonction du type de constructeur en cours d'exécution. Par conséquent, l'adaptation de votre exemple de syntaxe C++, vous auriez du appeler
BaseObject::LoadState()
. Vous ne pouvez pas obtenir àChildObject::LoadState()
, et d'essayer de le faire en spécifiant la classe ainsi que les résultats de la fonction de comportement indéfini.Les constructeurs de classes abstraites sont couverts dans la section 10.4, paragraphe 6. En bref, ils peuvent appeler des fonctions de membre, mais l'appel d'une fonction virtuelle pure dans le constructeur est un comportement indéfini. Ne pas le faire.
Si vous avez une classe comme indiqué dans votre post, qui prend un
XElement
dans le constructeur, la seule place quiXElement
pouvait venir est la classe dérivée. Alors pourquoi ne pas charger l'état dans la classe dérivée qui a déjà lesXElement
.Soit votre exemple manque certaines informations fondamentales qui change la situation, ou il n'y a pas besoin de dos de la chaîne jusqu'à la classe dérivée avec les informations de la classe de base, parce qu'il vient de vous dire que des informations exactes.
c'est à dire
Puis votre code est très simple, et vous n'avez pas du virtuel appel de la méthode des problèmes.
Pour le C++, lire Scott Meyer article correspondant :
Ne jamais Appeler des Fonctions Virtuelles en cours de Construction ou de Destruction
ps: attention à cette exception dans l'article:
Habituellement, vous pouvez obtenir autour de ces questions en ayant une plus gourmand constructeur de base. Dans votre exemple, vous êtes en passant un XElement à LoadState. Si vous permettre à l'etat d'être directement défini dans votre constructeur de base, puis votre enfant de la classe peut analyser la XElement avant de faire appel à votre constructeur.
Si l'enfant les besoins de la classe pour faire un bon peu de travail, il peut se défausser d'une méthode statique.
En C++, il est parfaitement possible d'appeler des fonctions virtuelles à partir de l'intérieur de la classe de base - aussi longtemps qu'ils sont non-pur - avec quelques restrictions. Cependant, vous ne devriez pas le faire. Mieux initialiser des objets à l'aide de non-fonctions virtuelles, qui sont explicitement marquées comme étant l'initialisation des fonctions à l'aide des commentaires et un nom approprié (comme
initialize
). Si c'est le même déclaré que pure virtual dans la classe de l'appelant, le comportement est indéfini.La version qui est appelé est celui de la classe de l'appeler à partir de l'intérieur du constructeur, et non pas quelque overrider dans certains de la classe dérivée. Cela n'a pas beaucoup à faire avec virtual tables de fonction, mais plus avec le fait que le remplacement de cette fonction pourrait appartenir à une classe qui n'est pas encore initialisé. C'est donc interdite.
En C# et Java, qui n'est pas un problème, car il n'y a pas une telle chose comme un défaut d'initialisation qui est fait juste avant d'entrer dans le corps du constructeur. En C#, les seules choses que l'on fait à l'extérieur du corps est l'appel de la classe de base ou de la fratrie des constructeurs, je crois. En C++, cependant, les initialisations fait pour les membres des classes dérivées par l'overrider de cette fonction serait annulée lors de la construction de ces membres lors du traitement du constructeur d'initialiseur de liste juste avant d'entrer dans les constructeurs de corps de la classe dérivée.
Modifier: en Raison d'un commentaire, je pense qu'un peu de clarification est nécessaire. Voici un (fictive) par exemple, supposons qu'il serait autorisé à appeler virtuals, et l'appel seraient le résultat d'une activation de la finale overrider:
Que le problème peut en effet être résolu en changeant la Norme C++ et le laisser - réglage de la définition de constructeurs, durée de vie des objets et autres joyeusetés. Les règles devraient être faits pour définir ce
str = ...;
signifie pour un qui n'est pas encore construit de l'objet. Et de noter comment l'effet de celle-ci dépend alors de qui a appeléinit
. La fonction que nous obtenir ne justifie pas les problèmes que nous avons à résoudre ensuite. Donc, C++ simplement interdit la distribution dynamique tandis que l'objet est en cours de construction.