Pourquoi utiliser une méthode d'initialisation au lieu d'un constructeur?
Je viens de recevoir dans une nouvelle société et une grande partie de la base de code utilise des méthodes d'initialisation à la place des constructeurs.
struct MyFancyClass : theUberClass
{
MyFancyClass();
~MyFancyClass();
resultType initMyFancyClass(fancyArgument arg1, classyArgument arg2,
redundantArgument arg3=TODO);
//several fancy methods...
};
Ils m'ont dit que cela avait quelque chose à voir avec le calendrier. Que certaines choses doivent être faites après construction qui échouerait dans le constructeur. Mais la plupart des constructeurs sont vides et je ne vois vraiment pas une raison pour ne pas utiliser les constructeurs.
Je me tourne donc vers vous, oh assistants du C++: pourquoi voudriez-vous utiliser un init-méthode au lieu d'un constructeur?
- Si votre entreprise ne peut pas l'expliquer, je sens mauvais code.
- Ressemble à un
void
est manquant. - Je suis d'accord avec Mike. Commencer à chercher un emploi.
- Je suis un ingénieur. La société est une société d'ingénierie. Le Code peut être malodorantes, aussi longtemps que les algorithmes sont impressionnantes. Et ils le sont! (Si j'avais demandé de plus, ils ont expliqué plus loin. C'est juste que vous les gars peuvent expliquer mieux et plus vite, donc je vous ai demandé à la place ;-))
- Voir ce lien Wiki sur la dernière norme C++ qu'il n'en résout une raison valable pour les fonctions d'initialisation : en.wikipedia.org/wiki/C%2B%2B11#Object_construction_improvement
Vous devez vous connecter pour publier un commentaire.
Car ils disent de "timing", je suppose que c'est parce qu'ils veulent que leurs fonctions d'initialisation pour être en mesure d'appeler des fonctions virtuelles sur l'objet. Ce n'est pas dans un constructeur, parce que dans le constructeur de la classe de base, la classe dérivée en partie de l'objet "n'existe pas encore", et, en particulier, vous ne pouvez pas accéder à des fonctions virtuelles définies dans la classe dérivée. Au lieu de cela, la version de classe de base de la fonction est appelée, si elle est définie. Si ce n'est pas définie, ce qui implique que la fonction est virtuelle pure), vous obtenez un comportement indéfini.
L'autre raison pour laquelle les fonctions d'initialisation est un désir d'éviter les exceptions, mais c'est un peu old-school style de programmation (et si c'est une bonne idée, c'est un argument de son propre). Il n'a rien à voir avec des choses qui ne peuvent pas travailler dans un constructeur, plutôt à voir avec le fait que les constructeurs ne pouvez pas retourner une valeur d'erreur si quelque chose échoue. Donc, dans la mesure où vos collègues vous ont donné les vraies raisons, j'imagine que ce n'est pas elle.
Oui je peux penser à plusieurs, mais généralement ce n'est pas une bonne idée.
La plupart du temps, la raison invoquée est que vous ne les erreurs d'un rapport par le biais d'exceptions dans un constructeur (ce qui est vrai), alors qu'avec une méthode classique, vous pouvez retourner un code d'erreur.
Cependant, dans bien conçu OO-code le constructeur est responsable de l'établissement de la classe d'invariants. En permettant à un constructeur par défaut, vous permettez à une classe vide, vous devez donc modifier les invariants de sorte que est acceptée à la fois le "null" et le "significative" de la classe... et de l'utilisation de la classe doit d'abord s'assurer que l'objet a été construit correctement... c'est de la crasse.
Alors maintenant, nous allons démystifier les "raisons":
virtual
méthode: utiliser le Virtual Constructeur idiome.assert
au début de chaque méthode publique assurez-vous que l'objet est utilisable avant d'essayer de l'utiliser.operator=
(et le mettre en œuvre à l'aide de la copie et de swap idiome si le compilateur a généré version ne permet pas de répondre à votre besoin).Comme l'a dit, en général, mauvaise idée. Si vous voulez vraiment avoir "nul", de constructeur, de les rendre
private
et de l'utilisation du Générateur de méthodes. C'est aussi efficace avec NRVO... et vous pouvez retournerboost::optional<FancyObject>
dans le cas où la construction a échoué.operator=
"assign
méthode sur unvector
objet: en utilisantassign
vous ré-utiliser le déjà de stockage alloué, et donc ne nécessitent pas une fausse allocation de la mémoire. C'est difficile de le faire correctement, je suis d'accord que la copie et le swap devrait être utilisé dans le cas général.D'autres ont répertorié beaucoup de raisons possibles (et leurs propres explications de pourquoi la plupart de ces sont généralement pas une bonne idée). Permettez-moi de poster un exemple de (plus ou moins) l'utilisation de méthodes init, ce qui a à voir avec le calendrier.
Dans un projet précédent, nous avons eu beaucoup de classes de Services et d'objets, chacun de qui ont fait partie d'une hiérarchie, et renvois les uns les autres de diverses manières. Donc généralement, pour la création d'un ServiceA, vous avez besoin d'un service parent de l'objet, qui à son tour a besoin d'un conteneur de service, qui a déjà dépendait de la présence de certains services spécifiques (y compris éventuellement ServiceA lui-même) pendant la phase d'initialisation. La raison en est que lors de l'initialisation, la plupart des services a enregistré lui-même avec d'autres services comme les auditeurs à des événements spécifiques, et/ou de la notification de autres services sur les cas de succès de l'initialisation. Si les autres n'existaient pas au moment de la notification, l'enregistrement n'est pas arrivé, donc ce service ne serait pas recevoir des messages importants plus tard, lors de l'utilisation de l'application. Afin de briser la chaîne de dépendances circulaires, nous avons dû utiliser l'initialisation explicite des méthodes distinctes de constructeurs, ainsi efficacement de prise de service mondial de l'initialisation d'un processus en deux phases.
Ainsi, bien que cet idiome ne devraient pas être suivies en général, à mon humble avis il a certaines utilisations valides. Cependant, il est préférable de limiter son utilisation au minimum, à l'aide de constructeurs chaque fois que possible. Dans notre cas, c'était un projet de l'héritage, et nous n'avons pas encore pleinement comprendre son architecture. Au moins l'utilisation de méthodes init a été limitée au service des classes - les classes régulières ont été initialisés par les constructeurs. Je crois qu'il pourrait être une façon de refactoriser que l'architecture d'éliminer la nécessité pour le service init méthodes, mais au moins, je ne vois pas comment le faire (et pour être franc, nous avons eu des questions plus urgentes à traiter à l'époque, je faisais partie du projet).
Deux raisons que je peux penser à du haut de ma tête:
Un de plus l'utilisation de cette initialisation peut être dans l'objet de la piscine. Fondamentalement, vous avez juste à demander à l'objet de la piscine. La piscine sera déjà un certain N objets créés qui sont vides. C'est l'appelant qui peut appeler n'importe quelle méthode il/elle aime l'ensemble des membres. Une fois que l'appelant a fait l'objet qu'elle va dire à la piscine pour destory il. L'avantage, c'est jusqu'à ce que l'objet est utilisé, la mémoire sera sauvé, et l'appelant peut alors utiliser sa propre adapté la méthode de membre de l'initialisation de l'objet. Un objet peut être de servir de beaucoup de but, mais l'appelant ne peut pas besoin de tous, et peut aussi ne pas besoin d'initialiser tous les membres de la objets.
Pensent généralement de connexions de base de données. Une piscine peut avoir des tas de connexion de l'objet, et l'appelant peut remplir le nom d'utilisateur, mot de passe etc.
fonction init() sont bonnes lorsque votre compilateur ne supporte pas les exceptions, ou de votre application cible ne peut pas utiliser un segment (exception sont généralement mises en œuvre à l'aide d'un tas de créer et de détruire).
init() routines sont également utiles lorsque l'ordre de la construction doit être défini. C'est-à-dire, si vous globalement allouer des objets, l'ordre dans lequel le constructeur est invoquée n'est pas défini. Par exemple:
La norme fournit aucune garantie que must_construct_before_instance1s'constructeur sera appelé avant instance1s'constructeur. Lorsqu'il est lié au matériel, l'ordre dans lequel les choses initialiser peut être crucial.
Et aussi j'aime joindre un exemple de code pour répondre #1 --
Depuis aussi msdn dit :
Exemple :
L'exemple suivant illustre l'effet de la violation de cette règle. L'application de test crée une instance de DerivedType, ce qui provoque sa classe de base (BadlyConstructedType) constructeur à exécuter. BadlyConstructedType du constructeur appelle incorrectement la méthode virtuelle DoSomething. Alors que la sortie des spectacles, DerivedType.DoSomething() s'exécute, et avant DerivedType du constructeur s'exécute.
De sortie :
Appel de base ctor.
Dérivés DoSomething est appelé - initialisé ? Pas de
Appel dérivés ctor.
Plus d'un cas spécial: Si vous créez un écouteur, vous souhaitez peut-être s'inscrire quelque part (comme avec un singleton ou GUI). Si vous le faites au cours de son constructeur, il fuit un pointeur/référence à lui-même, qui n'est pas encore sûr, car le constructeur n'a pas terminé (et pourrait même échouer complètement).
Assumer le singleton qui recueille tous les auditeurs et les envoie événements quand les choses se reçoit et de l'événement, puis parcourt la liste de ses auditeurs (l'un d'eux est le cas, nous parlons), pour les envoyer à chacun un message. Mais cette instance est encore à mi-chemin dans son constructeur, donc, l'appel peut échouer dans toutes sortes de mauvaises manières. Dans ce cas, il est utile d'avoir l'inscription dans une fonction distincte, qui de toute évidence vous ne pas appel du constructeur lui-même (qui irait à l'encontre de l'objectif tout à fait), mais de l'objet parent, une fois la construction terminée.
Mais c'est un cas particulier, pas le général.
Il est utile pour faire de la gestion des ressources. Dire que vous avez cours avec les destructeurs automatiquement de libérer des ressources lorsque l'objet de la durée de vie est plus. Dire que vous aussi vous avez une classe qui détient ces ressources classes, et de vous initier dans le constructeur de cette classe supérieure. Ce qui se passe lorsque vous utilisez l'opérateur d'affectation pour initier cette classe supérieure? Une fois que le contenu est copié, la vieille classe supérieure devient hors de leur contexte, et les destructeurs sont appelés pour toutes les classes de ressources. Si ces classes ont des pointeurs qui ont été copiés lors de l'affectation, puis tous ces pointeurs sont maintenant mauvais pointeurs. Si à la place vous initier à la ressource classes dans une autre fonction init dans la classe supérieure, vous contourner complètement les ressources de la classe de destructeur de jamais être appelé, parce que l'opérateur d'affectation n'a jamais de créer et de supprimer ces classes. Je crois que c'est ce que l'on entend par le "timing" exigence.
Vous utilisez une méthode d'initialisation au lieu de le constructeur si les initialiser doit être appelée APRÈS la classe a été créée. Donc, si Une classe a été créée:
et la initisalizer de catégorie A exigé qu'un être ensemble, alors évidemment, vous besoin de quelque chose comme: