@SebastianRedl a déjà donné des réponses simples et directes, mais des explications supplémentaires pourraient être utiles.
TL; DR = il existe une règle de style pour garder les constructeurs simples, il y a des raisons à cela, mais ces raisons se rapportent principalement à un style de codage historique (ou tout simplement mauvais). La gestion des exceptions dans les constructeurs est bien définie et les destructeurs seront toujours appelés pour les variables et membres locaux entièrement construits, ce qui signifie qu'il ne devrait pas y avoir de problèmes dans le code C ++ idiomatique. La règle de style persiste de toute façon, mais normalement ce n'est pas un problème - toute l'initialisation ne doit pas être dans le constructeur, et en particulier pas nécessairement ce constructeur.
C'est une règle de style courante que les constructeurs doivent faire le minimum absolu pour configurer un état valide défini. Si votre initialisation est plus complexe, elle doit être gérée en dehors du constructeur. S'il n'y a pas de valeur bon marché à initialiser que votre constructeur peut configurer, vous devez affaiblir les invarants imposés par votre classe pour en ajouter un. Par exemple, si l'allocation de stockage pour votre classe à gérer est trop coûteuse, ajoutez un état non alloué mais encore nul, car bien sûr, avoir des états spéciaux comme null n'a jamais posé de problème à personne. Ahem.
Bien que commun, certainement sous cette forme extrême, il est très loin d'être absolu. En particulier, comme mon sarcasme l'indique, je suis dans le camp qui dit que l'affaiblissement des invariants est presque toujours un prix trop élevé. Cependant, il existe des raisons derrière la règle de style, et il existe des moyens d'avoir à la fois des constructeurs minimaux et des invariants forts.
Les raisons sont liées au nettoyage automatique des destructeurs, en particulier face aux exceptions. Fondamentalement, il doit y avoir un point bien défini lorsque le compilateur devient responsable de l'appel des destructeurs. Pendant que vous êtes encore dans un appel constructeur, l'objet n'est pas nécessairement entièrement construit, il n'est donc pas valide d'appeler le destructeur pour cet objet. Par conséquent, la responsabilité de la destruction de l'objet n'est transférée au compilateur que lorsque le constructeur réussit. Ceci est connu sous le nom RAII (Resource Allocation Is Initialization) qui n'est pas vraiment le meilleur nom.
Si une levée d'exception se produit à l'intérieur du constructeur, tout ce qui est partiellement construit doit être explicitement nettoyé, généralement dans a try .. catch
.
Cependant, les composants de l'objet qui ont déjà été construits avec succès sont déjà la responsabilité des compilateurs. Cela signifie qu'en pratique, ce n'est pas vraiment un gros problème. par exemple
classname (args) : base1 (args), member2 (args), member3 (args)
{
}
Le corps de ce constructeur est vide. Tant que les constructeurs sont base1
, member2
et member3
sont exceptionnellement sûrs, il n'y a rien à craindre. Par exemple, si le constructeur de member2
lancers, ce constructeur est responsable de se nettoyer. La base base1
était déjà complètement construite, donc son destructeur sera automatiquement appelé. member3
n'a jamais été même partiellement construit, il n'a donc pas besoin d'être nettoyé.
Même quand il y a un corps, les variables locales qui ont été entièrement construites avant que l'exception ne soit levée seront automatiquement détruites, comme toute autre fonction. Les corps de constructeurs qui jonglent avec des pointeurs bruts ou qui "possèdent" une sorte d'état implicite (stockés ailleurs) - ce qui signifie généralement qu'un appel de fonction de début / acquisition doit être mis en correspondance avec un appel de fin / libération - peuvent provoquer des problèmes de sécurité exceptionnels, mais le vrai problème là-bas ne parvient pas à gérer correctement une ressource via une classe. Par exemple, si vous remplacez des pointeurs bruts par unique_ptr
dans le constructeur, le destructeur de unique_ptr
sera appelé automatiquement si nécessaire.
Il y a encore d'autres raisons pour lesquelles les gens préfèrent les constructeurs faisant le minimum. L'une est simplement parce que la règle de style existe, beaucoup de gens supposent que les appels de constructeur sont bon marché. Une façon d'avoir cela, mais d'avoir toujours des invariants forts, est d'avoir une classe d'usine / constructeur distincte qui a les invariants affaiblis à la place, et qui définit la valeur initiale nécessaire en utilisant (potentiellement beaucoup) des appels de fonction membre normaux. Une fois que vous avez l'état initial dont vous avez besoin, passez cet objet comme argument au constructeur de la classe avec les invariants forts. Cela peut "voler les tripes" de l'objet à invariants faibles - déplacer la sémantique - ce qui est une opération bon marché (et généralement noexcept
).
Et bien sûr, vous pouvez envelopper cela dans une make_whatever ()
fonction, afin que les appelants de cette fonction n'aient jamais besoin de voir l'instance de classe invariants affaiblis.