J'ai une classe CPP dont le constructeur effectue certaines opérations. Certaines de ces opérations peuvent échouer. Je sais que les constructeurs ne retournent rien.
Mes questions sont,
Est-il autorisé d'effectuer d'autres opérations que l'initialisation des membres dans un constructeur?
Est-il possible de dire à la fonction appelante que certaines opérations dans le constructeur ont échoué?
Puis-je faire
new ClassName()
retourner NULL si des erreurs se produisent dans le constructeur?
object-oriented
c++
MayurK
la source
la source
Square
, avec un constructeur qui prend un paramètre, la longueur d'un côté, vous voulez vérifier si cette valeur est supérieure à 0.Réponses:
Oui, bien que certaines normes de codage puissent l'interdire.
Oui. La méthode recommandée consiste à lever une exception. Vous pouvez également stocker les informations d'erreur à l'intérieur de l'objet et fournir des méthodes pour accéder à ces informations.
Non.
la source
Vous pouvez créer une méthode statique qui effectue le calcul et retourne un objet en cas de succès ou non en cas d'échec.
Selon la façon dont cette construction de l'objet est effectuée, il peut être préférable de créer un autre objet qui permet la construction d'objets dans une méthode non statique.
Appeler indirectement un constructeur est souvent appelé une «usine».
Cela vous permettrait également de renvoyer un objet null, ce qui pourrait être une meilleure solution que de retourner null.
la source
NULL
. Par exemple,int foo() { return NULL
vous renverriez en fait0
(zéro), un objet entier. Enstd::string foo() { return NULL; }
vous auriez accidentellement appeler cestd::string::string((const char*)NULL)
qui est un comportement non défini (NULL ne pointe pas vers une chaîne terminée par 0-\).int
. Par exemple,std::allocator<int>
c'est une usine parfaitement saine d'esprit.@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
Le corps de ce constructeur est vide. Tant que les constructeurs sont
base1
,member2
etmember3
sont exceptionnellement sûrs, il n'y a rien à craindre. Par exemple, si le constructeur demember2
lancers, ce constructeur est responsable de se nettoyer. La basebase1
é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 deunique_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.la source
main
fonction ou aux variables statiques / globales. Un objet alloué à l' aidenew
, n'est pas détenu jusqu'à ce que vous attribuez cette responsabilité, mais des pointeurs intelligents possèdent les objets tas alloués auxquels ils font référence, et les conteneurs possèdent leurs structures de données. Les propriétaires peuvent choisir de supprimer tôt, le destructeur des propriétaires est le responsable ultime.