7
votes

Arguments contre "initialiser ()" méthode au lieu des constructeurs

Je suis actuellement responsable de la recherche de toutes les mauvaises pratiques de notre base de code et de convaincre mes collègues de fixer le code fautif. Au cours de mes spéléologues, j'ai remarqué que beaucoup de gens ici utilisent ici le motif suivant:

class Foo
{
  public:
    Foo() { /* Do nothing here */ }
    bool initialize() { /* Do all the initialization stuff and return true on success. */ }
    ~Foo() { /* Do all the cleanup */ }
};


6 commentaires

FTR, il est connu comme une initialisation en deux phases.


Voici une question évidente: si la construction échoue, Qu'est-ce que vos collègues veulent faire avec l'objet à mi-chemin ? C'est inutile. Cela pourrait aussi bien avoir projeté une exception sur la construction


YPB! J'ai été dans cette position et c'est comme la troupeau de chats.


Je viens de trouver ce duplicata: Devrais-je utiliser des fonctions virtuelles 'initialisez ()' pour initialiser un objet de ma classe?


Question similaire avec de bonnes réponses: Pourquoi utiliser une méthode d'initialisation au lieu de Un constructeur?


Voir Stackoverflow.com/a/3786967/545127


6 Réponses :


5
votes

Ceci est généralement appelé initialisation de deux phases ou multiphase et est particulièrement mauvais car une fois qu'un appel de constructeur a terminé avec succès, vous devez disposer d'un objet prêt à l'emploi, dans ce cas, vous n'aurez pas de prêt à l'emploi. objet.

Je ne peux pas m'empêcher de souligner plus sur ce qui suit:
Lancer une exception du constructeur en cas de défaillance est le meilleur et le seul moyen concis de manipuler les défaillances de la construction d'objets.


0 commentaires

0
votes

Cela dépend du cas.

Si un constructeur peut échouer à cause de quelques arguments, une exception doit être lancée. Mais, bien sûr, vous devez vous documenter pour lancer des exceptions des constructeurs.

Si FOO contient des objets, ils seront initialisés deux fois, une fois dans le constructeur, une fois dans la méthode initialize , donc c'est un inconvénient.

imo, le plus grand inconvénient est que vous devez vous rappeler d'appeler initialiser . Quel est le point de créer un objet s'il est invalide?

Donc, si leur seul argument est qu'ils ne veulent pas lancer d'exceptions du constructeur, c'est un très mauvais argument.

Si, cependant, ils veulent une sorte d'initialisation paresseuse, elle est valide.


0 commentaires

0
votes

C'est une douleur, mais vous n'avez pas d'autre choix si vous souhaitez éviter de jeter une exception du constructeur. Il existe également une autre option, également douloureuse: faites toute l'initialisation du constructeur, puis vous devez vérifier si l'objet a été construit de manière successivement (E.G. Opérateur de conversion vers BOOL ou ISOK Méthode). La vie est difficile, ..... et alors tu meurs: (


6 commentaires

Mais si un objet n'a pas pu être initialisé (et donc, n'a pas pu être construit), quelles utilisations ont-elle de toute façon? Pour moi, la méthode iSOK est également fausse.


L'idée est que dans le constructeur, vous ne définissez que tous les membres aux valeurs initiales (elles sont donc toutes en état définies), vous invoquez initialiser qui peut (potentiellement) lancer une exception. Juste après le constructeur, le isok (ou opérateur bool) renvoie false. L'objet peut toujours être initialisé à certaines valeurs initiales (connues). Je ne discute pas que c'est une bonne idée, c'est parfois la seule alternative sane. BTW: Si vous souhaitez attraper une exception projetée du constructeur dans la liste de la rindialisation, la syntaxe devient vraiment laid - des deux maux, je choisirais l'ISOK / OPER_BOOL.


Mais qu'est-ce que l'objet représente après avoir été construit mais avant d'appeler initialiser () ?


Il représente un objet vide , mais comme j'ai écrit ci-dessus tous ses membres sont en état défini (par exemple, tous les pointeurs définis sur NULL, etc.). Même lorsque vous avez débogué de telles choses, vous pouvez voir clairement ce qui a été défini et ce qui n'est pas. Un autre argument pourrait être une initialisation paresseuse mentionnée ci-dessous par luchian grigore .


En effet. Cela peut être utile dans certains cas alors. Mais qu'en est-il des cas où un objet "vide" n'a pas de sens?


Bien sûr, il est possible que l'objet vide n'ait pas de sens, mais c'est votre code et vos décisions. Votre code est votre code qui crée des objets, vous pouvez donc toujours vérifier si tout va bien ou non - si ce n'est pas correct, vous êtes libre d'interdire l'utilisation de l'objet ou de lancer une exception à partir d'un endroit plus pratique (par exemple. Pas de constructeur). Même si l'objet vide est utilisé, il ne rien , mais son utilisation est sûre en ce sens qu'il ne plaçonnera pas, car il est (partiellement) udefin.



7
votes

Initialisation de l'étape unique (constructeur) et à deux étapes (avec une méthode init) L'initialisation sont des modèles utiles. Personnellement, je pense que, à l'exclusion, c'est une erreur, bien que si vos conventions interdisent l'utilisation des exceptions entièrement, vous interdisez l'initialisation d'une seule étape pour les constructeurs pouvant échouer.

En général, je préfère une première étape d'initialisation car cela signifie que vos objets peuvent avoir invariants plus forts. Je n'utilise que deux étapes d'initialisation lorsque je le considère significatif ou utile pour qu'un objet puisse exister dans un état "non initialisé".

avec une initialisation en deux étapes Il est valide pour votre objet d'être dans un état non initialisé - chaque méthode qui fonctionne avec l'objet doit être consciente et à gérer correctement le fait que cela pourrait être dans un état inintitualisé. Ceci est analogue à travailler avec des pointeurs, où il est médiocre de supposer qu'un pointeur n'est pas nul. Inversement, si vous faites toute votre initialisation dans votre constructeur et que vous échouez avec des exceptions que vous pouvez ajouter «l'objet est toujours initialisé» à votre liste d'invariants, et cela devient donc plus facile et plus sûr de faire des hypothèses sur l'état de l'objet.


0 commentaires

1
votes

Cela dépend de la sémantique de votre objet. Si l'initialisation est quelque chose qui est crucial pour la structure de données de la classe elle-même, une défaillance serait mieux traitée en lançant une exception à partir du constructeur (par exemple, si vous êtes hors mémoire), ou par une affirmation (si vous savez que votre code ne devrait pas réellement échouer, jamais).

D'autre part, si le succès ou autrement de la construction dépend de la saisie de l'utilisateur, la défaillance n'est pas une condition exceptionnelle, mais plutôt une partie du comportement d'exécution normal et attendu que vous besoin de tester pour. Dans ce cas, vous devez avoir un constructeur par défaut qui crée un objet dans un état "invalide" et une fonction d'initialisation pouvant être appelée dans un constructeur ou plus tard et que peut ne pas réussir. Prendre std :: ifstream comme exemple.

Donc, un squelette de votre classe pourrait ressembler à ceci: xxx


0 commentaires

0
votes

Je me rends compte que c'est un très vieux fil, mais je voulais ajouter quelque chose qui n'a pas été explicitement indiqué (ou peut-être simplement implicite). En C ++, lorsqu'un constructeur jette une exception, l'objet n'est pas considéré comme "construit" et donc son destructeur ne sera pas appelé comme faisant partie de l'exception déroulant.

Cela peut être un facteur de motivation très réel pour avoir une méthode initiale () au lieu de le faire dans le constructeur. Un objet complexe faisant beaucoup d'allocation de mémoire et il faudrait tout ce que tout ce qui fonctionne manuellement si le constructeur a jeté une exception.

Si une méthode initiale () est utilisée, l'objet est déjà "construit" au moment de l'initialisation, et donc le destructeur de l'objet sera appelé.

Donc, oui, faire l'initialisation dans le constructeur est "plus gentille", mais elle met également une charge accrue sur le programmeur pour nettoyer correctement si les choses vont mal. Une approche fragmentée du nettoyage permettra de créer un code très laid.

Dans certains cas, il pourrait donc être préférable d'accepter le pragmatisme sur l'idéalisme.


0 commentaires