En C ++, quels sont les avantages d'utiliser des exceptions et try / catch au lieu de simplement renvoyer un code d'erreur?

StackOverflow https://stackoverflow.com/questions/196522

Question

Je programme C et C ++ depuis longtemps et je n’ai jusqu’à présent jamais utilisé d’exceptions et try / catch. Quels sont les avantages d'utiliser cela au lieu de simplement renvoyer des codes d'erreur aux fonctions?

Était-ce utile?

La solution

Peut-être un point évident: un développeur peut ignorer (ou ne pas être au courant) de votre statut de retour et continuer à être parfaitement conscient que quelque chose a échoué.

Une exception doit être reconnue de quelque manière que ce soit - elle ne peut être ignorée en silence sans mettre activement en place quelque chose à cet effet.

Autres conseils

L'avantage des exceptions est double:

  • Ils ne peuvent pas être ignorés. Vous devez les traiter à un certain niveau, sinon ils mettront fin à votre programme. Avec le code d'erreur, vous devez les vérifier explicitement, sinon ils sont perdus.

  • Ils peuvent être ignorés. Si une erreur ne peut pas être traitée à un niveau donné, elle sera automatiquement affichée au niveau suivant, où elle peut se trouver. Les codes d'erreur doivent être explicitement passés jusqu'à ce qu'ils atteignent le niveau auquel ils peuvent être traités.

L'avantage est que vous n'avez pas à vérifier le code d'erreur après chaque appel potentiellement défaillant. Cependant, pour que cela fonctionne, vous devez le combiner avec les classes RAII afin que tout soit automatiquement nettoyé au fur et à mesure que la pile se déroule.

Avec les messages d'erreur:

int DoSomeThings()
{
    int error = 0;
    HandleA hA;
    error = CreateAObject(&ha);
    if (error)
       goto cleanUpFailedA;

    HandleB hB;
    error = CreateBObjectWithA(hA, &hB);
    if (error)
       goto cleanUpFailedB;

    HandleC hC;
    error = CreateCObjectWithA(hB, &hC);
    if (error)
       goto cleanUpFailedC;

    ...

    cleanUpFailedC:
       DeleteCObject(hC);
    cleanUpFailedB:
       DeleteBObject(hB);
    cleanUpFailedA:
       DeleteAObject(hA);

    return error;
}

Avec exceptions et RAII

void DoSomeThings()
{
    RAIIHandleA hA = CreateAObject();
    RAIIHandleB hB = CreateBObjectWithA(hA);
    RAIIHandleC hC = CreateCObjectWithB(hB);
    ...
}

struct RAIIHandleA
{
    HandleA Handle;
    RAIIHandleA(HandleA handle) : Handle(handle) {}
    ~RAIIHandleA() { DeleteAObject(Handle); }
}
...

À première vue, la version RAII / Exceptions semble plus longue, jusqu'à ce que vous réalisiez que le code de nettoyage ne doit être écrit qu'une seule fois (et qu'il existe des moyens de le simplifier). Mais la deuxième version de DoSomeThings est beaucoup plus claire et facile à gérer.

N'essayez PAS d'utiliser les exceptions en C ++ sans l'idiome RAII, car vous perdriez des ressources et de la mémoire. Tout votre nettoyage doit être effectué dans les destructeurs d’objets alloués à la pile.

Je me rends compte qu'il existe d'autres moyens de gérer le code d'erreur, mais ils finissent tous par se ressembler. Si vous laissez tomber les commentaires, vous finissez par répéter le code de nettoyage.

Un point intéressant pour les codes d’erreur, c’est qu’ils indiquent clairement où les choses peuvent échouer et comment elles peuvent échouer. Dans le code ci-dessus, vous écrivez avec l'hypothèse que les choses ne vont pas échouer (mais si c'est le cas, vous serez protégé par les wrappers RAII). Mais vous finissez par accorder moins d’attention aux endroits où les choses peuvent mal tourner.

La gestion des exceptions est utile car elle permet de séparer facilement le code de gestion des erreurs du code écrit pour gérer la fonction du programme. Cela facilite la lecture et l’écriture du code.

Hormis les autres éléments mentionnés, vous ne pouvez pas renvoyer un code d'erreur d'un constructeur. Les destructeurs non plus, mais vous devriez également éviter de lancer une exception à partir d’un destructeur.

  • renvoie un code d'erreur lorsqu'une condition d'erreur est attendue dans certains cas
  • lève une exception lorsqu'une condition d'erreur n'est pas attendue dans tous les cas

dans le premier cas, l'appelant de la fonction doit vérifier le code d'erreur pour l'échec attendu; dans ce dernier cas, l’exception peut être traitée par n’importe quel appelant dans la pile (ou par le gestionnaire par défaut) comme il convient

J'ai écrit une entrée de blog à ce sujet ( Exceptions make for Elegant Code ), qui a ensuite été publié dans Surcharge . En fait, j’ai écrit ceci en réponse à quelque chose que Joel a dit sur le podcast StackOverflow!

Quoi qu'il en soit, je crois fermement que les exceptions sont préférables aux codes d'erreur dans la plupart des cas. Je trouve très pénible d’utiliser des fonctions qui renvoient des codes d’erreur: vous devez vérifier le code d’erreur après chaque appel, ce qui peut perturber le flux du code appelant. Cela signifie également que vous ne pouvez pas utiliser d’opérateurs surchargés, car il n’ya aucun moyen de signaler l’erreur.

La peine de vérifier les codes d'erreur signifie que les utilisateurs négligent souvent de le faire, les rendant ainsi totalement inutiles: au moins, vous devez ignorer explicitement les exceptions avec une instruction catch .

L’utilisation de destructeurs en C ++ et d’éliminateurs en .NET pour s’assurer que les ressources sont correctement libérées en présence d’exceptions peut également grandement simplifier le code. Afin d’obtenir le même niveau de protection que les codes d’erreur, vous avez besoin de nombreuses instructions si , de nombreux codes de nettoyage dupliqués ou d'appels goto vers un bloc de nettoyage commun à la fin d'une fonction. Aucune de ces options n’est agréable.

Voici une bonne explication de EAFP (" Plus facile de demander pardon que Permission. & Quot;), qui, je pense, s’applique ici même s’il s’agit d’une page Python dans Wikipedia. L'utilisation d'exceptions conduit à un style de codage plus naturel, IMO - et de l'avis de beaucoup d'autres également.

Quand j’enseignais le C ++, notre explication standard était qu’ils vous permettaient d’empêcher d’embrouiller les scénarios de journées ensoleillées et pluvieuses. En d’autres termes, vous pourriez écrire une fonction comme si tout allait bien se passer et attraper l’exception à la fin.

Sans exception, vous devez obtenir une valeur de retour pour chaque appel et vous assurer qu'il est toujours légitime.

Un avantage connexe, bien sûr, est que vous ne "gaspillez" pas. votre valeur de retour sur les exceptions (et ainsi permettre aux méthodes qui devraient être void d'être void), et peut également renvoyer des erreurs de constructeurs et de destructeurs.

Le Guide de style C ++ de Google est très complet analyse des avantages et inconvénients de l'utilisation des exceptions dans le code C ++. Il indique également certaines des grandes questions que vous devriez poser; est-ce que j’ai l’intention de distribuer mon code à d’autres personnes (qui peuvent avoir des difficultés à s’intégrer à une base de code activée par une exception)?

Parfois, vous devez vraiment utiliser une exception pour signaler un cas exceptionnel. Par exemple, si quelque chose ne va pas dans un constructeur et que vous trouvez qu'il est judicieux d'en informer l'appelant, vous n'avez pas d'autre choix que de lever une exception.

Autre exemple: il n’ya parfois aucune valeur que votre fonction peut retourner pour indiquer une erreur; toute valeur que la fonction peut renvoyer indique un succès.

int divide(int a, int b)
{
    if( b == 0 )
        // then what?  no integer can be used for an error flag!
    else
        return a / b;
}

Le fait que vous deviez acquitter les exceptions est correct, mais cela peut également être implémenté à l'aide de structures d'erreur. Vous pouvez créer une classe d'erreur de base qui vérifie dans son dtor si une certaine méthode (par exemple, IsOk) a été appelée. Sinon, vous pouvez enregistrer quelque chose et ensuite quitter, ou lever une exception, ou lever une assertion, etc ...

Le simple fait d'appeler l'IsOk sur l'objet d'erreur sans y réagir serait alors l'équivalent d'écrire catch (...) {} Les deux déclarations afficheraient le même manque de bonne volonté du programmeur.

Le transport du code d'erreur jusqu'au niveau correct est une préoccupation majeure. Il faudrait en gros que presque toutes les méthodes renvoient un code d'erreur pour la seule raison de la propagation. Mais là encore, une fonction ou une méthode doit toujours être annotée avec les exceptions qu’elle peut générer. Donc, fondamentalement, vous avez le même problème, sans interface pour le prendre en charge.

Comme l'a souligné @Martin, le fait de lancer des exceptions oblige le programmeur à gérer l'erreur. Par exemple, ne pas vérifier les codes de retour est l’une des plus grandes sources de failles de sécurité dans les programmes C. Les exceptions garantissent que vous gérez l'erreur (espérons-le) et fournissez une sorte de chemin de récupération pour votre programme. Et si vous choisissez d'ignorer une exception plutôt que d'introduire un trou de sécurité, votre programme se bloque.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top