5 votes

Gestion des erreurs en C++, constructeurs et méthodes ordinaires

J'ai un cheesesales.txt Un fichier CSV contenant toutes mes ventes récentes de fromage. Je veux créer une classe CheeseSales qui peuvent faire des choses comme celles-ci :

CheeseSales sales("cheesesales.txt"); //has no default constructor
cout << sales.totalSales() << endl;
sales.outputPieChart("piechart.pdf");

Le code ci-dessus suppose qu'aucune défaillance ne se produira. En réalité, des défaillances se produiront. Dans ce cas, deux types d'échecs peuvent se produire :

  • Échec dans le Constructeur : Le fichier n'existe peut-être pas, n'a peut-être pas les autorisations de lecture, contient des données non valides/non analysables, etc.
  • Échec dans le méthode habituelle : Le fichier peut déjà exister, il se peut qu'il n'y ait pas d'accès en écriture, trop peu de données de vente disponibles pour créer un graphique circulaire, etc.

Ma question est simple : comment concevriez-vous ce code pour gérer les échecs ?

Une idée : Renvoyer un bool de la méthode habituelle indiquant un échec. Je ne sais pas comment gérer le constructeur.

Comment les codeurs C++ expérimentés font-ils ce genre de choses ?

4voto

zen-cat Points 395

En C++, les exceptions sont le moyen de signaler les erreurs. L'exception BTW dans la liste d'initialisation PEUT être traitée.

Un bloc de fonction-essai associe un seq de gestionnaire à la fonction ctor-initializer, si présent, et le corps de la fonction. Une exception exception levée pendant l'exécution des expressions de l'initialisateur dans le ctor-initializer ou pendant l'exécution du corps de la fonction. transfère le contrôle à un gestionnaire dans un bloc d'essai de la même manière de la même manière qu'une exception levée pendant l'exécution d'un bloc d'essai. transfère le contrôle à d'autres gestionnaires.

Un bon code doit généralement utiliser un minimum de blocs try/catch au niveau le plus élevé (thread). Idéalement, un seul. De cette façon, sachant que "tout peut être rejeté", vous ne devez pas trop penser aux erreurs et le flux de code de votre scénario normal semble propre.

2voto

giorashc Points 8238

Un constructeur est utilisé pour initialiser l'état interne de l'objet. Ne l'utilisez pas pour effectuer des opérations lourdes comme la lecture et le traitement de fichiers.

Utilisez plutôt une méthode qui lit le fichier et lève une exception (ou renvoie un booléen en cas de succès) si une erreur se produit. Attrapez cette exception dans votre flux principal et traitez-la comme bon vous semble.

EDIT : Si c'est le but de la classe, alors peut-être ChessSales ne doit contenir que les données et vous devez utiliser une balise classe d'usine (ou peut-être Classe utilitaire statique ) qui dispose d'une méthode permettant de lire un fichier CSV et de renvoyer un fichier de type ChessSales contenant les données pertinentes lues dans le fichier CSV. De cette façon, vous séparez vos données de la logique de gestion (dans ce cas, la lecture et l'analyse du fichier CSV).

2voto

hyde Points 13720

Eh bien, lancer une exception est le choix évident. Cela pose quelques problèmes en C++, car il n'est pas possible d'attraper les exceptions lancées par les constructeurs de listes d'initialisation, ce qui entraîne toutes sortes de problèmes.

Vous devez donc fournir à la fois un constructeur qui accède au fichier et peut lever une exception, et un constructeur par défaut qui laisse l'objet dans l'état "données non chargées". Cela permet d'utiliser votre objet en toute sécurité en tant que membre d'autres classes, tout en permettant aux données d'être chargées (ou aux exceptions d'être levées) par un autre constructeur.

Un autre choix est de faire en sorte que le constructeur de chargement de données ne lève pas l'exception, mais mette l'objet dans un état invalide si le chargement échoue, et que les autres méthodes lèvent l'exception, et aient un getter pour l'état actuel.

Dans tous les cas, vous avez besoin d'un état d'erreur/non initialisé pour votre classe, il n'y a pas de moyen sûr de contourner cela, je crois.

Modifié par les commentaires de @MathieuM. : une alternative pour réaliser l'"état non initialisé" en externe est de le rendre optionnel, le plus facilement en utilisant une classe wrapper de pointeur. Il est alors initialisé en toute sécurité à NULL dans la liste des initialisateurs, et la véritable initialisation est tentée dans le corps du constructeur, avec une gestion des erreurs quelconque. En choisissant cette méthode, vous pouvez simplement faire en sorte que le constructeur lève une exception, et laisser les utilisateurs de la classe s'en soucier.

2voto

Matthieu M. Points 101624

Vous avez raison de distinguer les deux types d'échec, ils sont en effet subtilement différents.


Échec dans le Constructeur : Le fichier peut ne pas exister, ne pas avoir les autorisations de lecture, contenir des données non valides/non analysables, etc.

Juste lancez une exception. Les objets à moitié construits sont le chemin le plus court vers des programmes truffés de bogues.

Une classe doit avoir un invariant que le constructeur établit et que toutes les méthodes maintenir . Si le constructeur ne peut pas établir l'invariant, alors l'objet est inutilisable et la meilleure façon de le signaler, en C++, est de lancer une exception afin que le langage garantisse que l'objet à moitié construit ne sera pas utilisé.

Si les gens suggèrent que vous pourriez avoir besoin d'un état invalide rappelez-leur les Principe de responsabilité unique : votre classe a déjà une responsabilité spécifique (son invariant), ceux qui souhaitent plus peuvent encapsuler dans une classe dédiée à fournir optionalité . De ma tête, boost::optional y std::unique_ptr sont tous deux d'excellents choix.


Échec de la méthode habituelle : Le fichier peut déjà exister, il se peut qu'il n'y ait pas d'accès en écriture, trop peu de données de vente disponibles pour créer un graphique circulaire, etc.

Malheureusement, vous n'avez pas réussi à faire la distinction entre deux cas :

  • des méthodes qui ne sont jamais lire l'instance
  • qui modifient également l'instance

Pour toutes les méthodes, vous devez choisir votre stratégie de signalement des erreurs. Mon conseil est le suivant exceptions son exceptionnel . Si l'échec est considéré comme exceptionnel (lien réseau en panne alors qu'il est en place 99,99% du temps), alors une exception est acceptable. D'un autre côté, si l'échec est attendu, généralement en fonction de l'entrée telle qu'une find ou, dans votre cas, une méthode write à un fichier spécifié, vous voulez alors donner à l'utilisateur la possibilité de réagir de manière appropriée.

Il reste au moins deux possibilités, après avoir écarté les exceptions :

  • renvoyant un code ( bool , enum ) indiquant si l'opération s'est bien déroulée ou non
  • demandant à l'utilisateur de fournir un politique d'erreur qui sera invoqué en cas de problème

La politique d'erreur peut être aussi simple qu'une enum (skip, retry-once, throw) ou aussi compliquée qu'un véritable Strategy avec différentes méthodes.

De plus, personne ne dit que votre méthode ne peut avoir que un mécanisme de signalement des erreurs. Par exemple, vous pouvez choisir :

  • pour invoquer une politique d'erreur si le fichier existe déjà (fourni par l'utilisateur après tout, il pourrait vouloir changer de nom)
  • à lancer si le disque n'est pas accessible (on s'attend généralement à ce que le matériel fonctionne !)

Enfin, en plus de cela, les méthodes qui modifient également l'instance doivent se préoccuper des éléments suivants maintenir l'invariant que le constructeur a établi. Si une méthode peut bousiller l'invariant, alors ce n'est pas un invariant du tout, et vos utilisateurs devraient trembler de peur à chaque fois que la classe est utilisée... La sagesse générale est d'effectuer toutes les opérations qui pourraient lancer antes de commencer à modifier l'objet.

Une mise en œuvre simpliste (mais tellement facile) est la fonction copier et échanger idiome : copier l'objet en interne, effectuer les opérations sur la copie, et à la fin de la méthode échanger l'état de la copie avec l'état de l'objet courant. Si quelque chose ne va pas, la copie est corrompue et immédiatement éliminée pendant le déroulement de la pile, laissant l'objet intact.

Pour en savoir plus sur ce sujet, vous pouvez lire les articles suivants Exceptions Garanties . Ce que j'ai décrit est une mise en œuvre typique de la Garantie d'exception forte similaire aux transactions des bases de données (tout ou rien).

0voto

CodeChords man Points 3191

Voici comment je gère les erreurs : Je consigne toutes les erreurs dans un fichier journal et je crée une fonction simple à utiliser pour signaler les erreurs. Si le programme ne fonctionne pas bien, j'ouvre le fichier journal et j'en trouve la raison.

En ce qui concerne la manière d'être informé des erreurs lorsqu'elles se produisent : L'une des raisons de l'introduction des exceptions dans le C++ était de signaler les erreurs dans les constructeurs. Puisque les constructeurs ne retournent pas de valeurs, ils peuvent lancer des exceptions et signaler les échecs de cette façon. Personnellement, je n'aime pas trop ce système, parce qu'il vous oblige à placer votre code à l'intérieur de l'espace de construction. try..catch . Si vous oubliez de le faire, vous pouvez vous demander pourquoi votre programme s'est planté

Voici donc ce que je fais habituellement :

1) Laisser le constructeur définir une variable membre pour le succès/l'échec, en fonction d'une opération réussie du constructeur. Je peux ensuite le vérifier avec quelque chose comme if (myobj.constructor_ok()...) . Remarquez que je n'ai pas accédé directement à la variable membre.

2) J'aime beaucoup retourner le vrai/faux à partir des méthodes, en indiquant le succès/l'échec chaque fois que possible. Cela rend le code très lisible.

3) ..et le fichier journal ci-dessus.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X