322 votes

C++ : écrivez-vous (vraiment) code sécurisé d’exception ?

La gestion des exceptions (EH) semble être la norme actuelle, et en cherchant sur le web, je ne peux pas trouver tout de nouvelles idées ou des méthodes qui tentent de les améliorer ou de les remplacer (ainsi, certaines variations existent, mais rien de roman).

Si la plupart des gens semblent l'ignorer ou tout simplement l'accepter, HEIN a de gros inconvénients: les exceptions sont invisibles pour le code et il crée beaucoup, beaucoup de points de sortie. Joel on software a écrit un article à ce sujet. La comparaison d' goto s'adapte à la perfection, il m'a fait réfléchir à nouveau sur HEIN.

J'essaie d'éviter HEIN et il suffit d'utiliser les valeurs de retour, des rappels ou quoi que s'inscrit le but. Mais lorsque vous devez écrire du code fiable, vous ne pouvez pas ignorer HEIN ces jours-ci: Il commence avec l' new, qui peut lancer une exception, au lieu de simplement retourner 0 (comme dans les vieux jours). Cela fait environ n'importe quelle ligne de code C++ vulnérables à une exception. Et puis de plus en plus d'endroits dans le C++ fondamentale code de lever des exceptions... std lib t-il, et ainsi de suite.

Cela se sent comme marcher sur fragile motifs.. Donc, maintenant, nous sommes forcés de prendre soin sur les exceptions!

Mais c'est dur, vraiment dur. Vous devez apprendre à écrire à l'exception coffre-fort à code, et même si vous avez de l'expérience avec elle, il sera toujours nécessaire de vérifier une seule ligne de code pour en être sûr! Ou vous commencer à mettre les blocs try/catch partout, ce qui encombre le code jusqu'à ce qu'il atteigne un état de l'illisible.

EH remplacé l'ancien propre deterministical approche (les valeurs de retour..), qui avait juste un peu, mais compréhensible et facilement solveable inconvénients avec une approche qui crée de nombreux points de sortie dans votre code, et si vous commencez à écrire le code qui attrape les exceptions (ce que vous êtes obligés de faire à un certain point), puis il crée même une multitude de chemins à l'aide de votre code dans le bloc catch, que penser d'un programme serveur où vous avez besoin de journalisation des installations autres que les std::eree ..). EH a des avantages, mais ce n'est pas le point.

Mes questions:

  • Avez-vous vraiment écrire exception coffre-fort à code?
  • Êtes-vous sûr de votre dernière "production ready" code d'exception en sécurité?
  • Pouvez-vous être sûr que c'est?
  • Savez-vous et/ou réellement utiliser des alternatives qui fonctionnent?

Pro HEIN arguments comme "mais seulement dans des cas exceptionnels" ou "habituellement, vous aurez juste à la sortie de l'application, de toute façon" ou "ne pas utiliser les exceptions pour autre chose que des cas exceptionnels" ou "ne pas les utiliser pour modifier le flux du programme" sera ignoré ou même votée ;-)

EDIT: remplacé "SEH" avec "EH" (la gestion des exceptions) pour éviter la confusion.

EDIT 2: et Aussi, merci de ne pas écrire les réponses comme "il a travaillé pour XX nombre d'années...". Ce n'aide pas, et ce n'est pas le point. Les lois de Newton a également travaillé et sont toujours valables, même si nous savons qu'ils ne peuvent pas être la bonne chose.

EDIT 3: j'aimerais ajouter une autre question: Qui a inventé le "jeter/try/catch exception de la manipulation de" genre " d'erreur de manipulation, et quand et pour qui la langue? Toutes les sources connues?

526voto

paercebal Points 38526

Votre question fait une affirmation, que "l'Écriture exception coffre-fort à code est très dur". Je vais répondre à vos questions, d'abord, et ensuite, de répondre à la question cachée derrière eux.

Répondant à des questions

Avez-vous vraiment écrire exception coffre-fort à code?

Bien sûr, je ne.

C'est la raison Java perdu beaucoup de son attrait pour moi en tant que programmeur C++ (manque de RAII sémantique), mais je suis s'égarer: C'est un C++ question.

Il est en fait nécessaire lorsque vous devez travailler avec la STL ou de l'amplification de code. Par exemple, C++ threads (boost::thread ou std::thread) lèvera une exception de fermer correctement.

Êtes-vous sûr de votre dernière "production ready" code d'exception en sécurité?

Pouvez-vous être sûr que c'est?

L'écriture d'exception-safe code est comme l'écriture de code sans bug.

Vous ne pouvez pas être sûr à 100% votre code à l'exception de sécurité. Mais alors, vous vous efforcez pour elle, bien à l'aide de modèles connus, et en évitant bien connu des anti-modèles.

Savez-vous et/ou réellement utiliser des alternatives qui fonctionnent?

Il n'existe pas de solutions de rechange viables en C++ (c'est à dire que vous aurez besoin de revenir à C, et d'éviter les bibliothèques C++ ainsi que des surprises comme Windows SEH).

Écrit exception coffre-fort à code

À l'exception d'écriture coffre-fort à code, vous devez savoir d'abord quel est le niveau de l'exception de sécurité de chaque instruction que vous écrivez.

Par exemple, un new peut lancer une exception, mais l'affectation d'un élément (par exemple un int ou un pointeur) de ne pas échouer. Un swap ne manquera jamais (ne jamais écrire un lancer swap), std::list::push_back pouvez jeter...

Exception de garantie

La première chose à comprendre est que vous devez être en mesure d'évaluer l'exception de la garantie offerte par l'ensemble de vos fonctions:

  1. aucun : Votre code ne doit jamais offrir. Ce code est une fuite de tout, et de briser à la première exception levée.
  2. de base : C'est la garantie, vous devez à tout le moins offrir, qui est, si une exception est levée, les ressources ne sont pas fui, et tous les objets sont encore ensemble
  3. forte : Le traitement sera soit réussir, ou lever une exception, mais si il jette, puis les données seront dans le même état que si le traitement n'avait pas commencé du tout (ce qui donne une transactionnel de la puissance du C++)
  4. nothrow/nofail : Le traitement va réussir.

Exemple de code

Le code suivant semble correct C++, mais en vérité, propose le "non" de la garantie, et par conséquent, il n'est pas correct:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   X * x = new X() ;                // 2. basic : can throw with new and X constructor
   t.list.push_back(x) ;            // 3. strong : can throw
   x->doSomethingThatCanThrow() ;   // 4. basic : can throw
}

J'écris tout mon code avec ce genre d'analyse à l'esprit.

Les plus faibles la garantie offerte est de base, mais ensuite, la commande de chaque instruction rend l'ensemble de la fonction "none", parce que si 3. jette, x aura une fuite.

La première chose à faire serait de rendre la fonction "de base", qui est en train de mettre x dans un pointeur intelligent jusqu'à ce qu'il est en sécurité détenue par la liste:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   std::auto_ptr<X> x(new X()) ;    // 2.  basic : can throw with new and X constructor
   X * px = x.get() ;               // 2'. nothrow/nofail
   t.list.push_back(px) ;           // 3.  strong : can throw
   x.release() ;                    // 3'. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 4.  basic : can throw
}

Maintenant, notre code propose une "base" de garantie. Rien ne fuite, et tous les objets seront dans un état correct. Mais nous pourrions offrir le plus, c'est la forte garantie. C'est là que ça peut devenir coûteux, et c'est pourquoi, pas tout le code C++ est forte. Essayons:

void doSomething(T & t)
{
   // we create "x"
   std::auto_ptr<X> x(new X()) ;    // 1. basic : can throw with new and X constructor
   X * px = x.get() ;               // 2. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 3. basic : can throw

   // we copy the original container to avoid changing it
   T t2(t) ;                        // 4. strong : can throw with T copy-constructor

   // we put "x" in the copied container
   t2.list.push_back(px) ;          // 5. strong : can throw
   x.release() ;                    // 6. nothrow/nofail
   if(std::numeric_limits<int>::max() > t2.integer)  // 7.   nothrow/nofail
      t2.integer += 1 ;                              // 7'.  nothrow/nofail

   // we swap both containers
   t.swap(t2) ;                     // 8. nothrow/nofail
}

Nous re-commandé les opérations, de la création et de la configuration de X à sa juste valeur. Si tout échoue, alors t n'est pas modifié, donc, l'opération de 1 à 3 peut être considéré comme "fort": Si quelque chose de jette, t n'est pas modifié, et X n'aura pas de fuite parce qu'il est possédé par le pointeur intelligent.

Ensuite, nous créons une copie t2 de t, et de travailler sur cette copie à partir de l'exploitation de 4 à 7. Si quelque chose de jette, t2 est modifié, mais alors, t est encore à l'original. Nous offrons toujours le solide garantie.

Ensuite, on échange t et t2. Opérations de Swap doit être nothrow en C++, donc espérons que le swap que vous avez écrit pour T est nothrow (si elle n'est pas, la réécriture de sorte qu'il est nothrow).

Donc, si nous arrivons à la fin de la fonction, tout réussi (Pas besoin d'un type de retour) et t a ses l'exception de la valeur. Si elle échoue, alors t a encore sa valeur d'origine.

Maintenant, en offrant la forte garantie pourrait être assez cher, donc ne pas s'efforcer d'offrir la forte garantie pour tout votre code, mais si vous pouvez faire cela sans coût (et C++ inline et d'autres optimisation peut faire tout le code ci-dessus gratuit), puis le faire. La fonction de l'utilisateur vous en remercie.

Conclusion

Il prend l'habitude d'écrire du code garanti sans exception. Vous aurez besoin pour évaluer la garantie offerte par chaque instruction que vous utilisez, et ensuite, vous aurez besoin pour évaluer la garantie offerte par une liste d'instructions.

Bien sûr, le compilateur C++ ne sera pas sauvegarder la garantie (dans mon code, je vous offre la garantie d'un @avertissement doxygen tag), qui est un peu triste, mais il ne doit pas vous empêcher d'essayer d'écrire exception coffre-fort à code.

Normal d'échec vs bug

Comment un programmeur garantir qu'un nofail fonction est toujours une réussite? Après tout, la fonction pourrait avoir un bug.

Ce qui est vrai. L'exception des garanties sont censés être offerts par code sans bug. Mais alors, la langue de l'appel d'une fonction suppose que la fonction est exempt de bogues. Pas sain d'esprit code protège lui-même de la possibilité d'avoir un bug. Écrire du code du mieux que vous pouvez, et puis, offrent la garantie avec la supposition qu'il est exempt de bugs. Et si il y a un bug, la corriger.

Les Exceptions sont exceptionnelles de traitement de l'échec, pas de code pour les bugs.

Les derniers mots de

Maintenant, la question est "Est-ce la peine ?".

Bien sûr, il est. Avoir un "nothrow/nofail de la fonction" sachant que la fonction ne pas échouer est une grande bénédiction. La même chose peut être dite pour un "fort", une fonction qui vous permet d'écrire du code avec la sémantique transactionnelle, comme les bases de données, avec commit/rollback caractéristiques, la validation étant normale de l'exécution du code, de le jeter à l'exception de la restauration.

Ensuite, la "base" est le moins de garantie, vous devez offrir. C++ est un très bon langage, avec ses étendues, vous permettant d'éviter toutes fuites de ressource (quelque chose d'un garbage collector il serait difficile de proposer à la base de données, de connexion ou de descripteurs de fichiers).

Donc, tant que je le vois, il est en vaut la peine.

Edit 2010-01-29: à Propos de la non-lancer swap

nobar a fait un commentaire qui, je crois, est tout à fait pertinente, car elle fait partie de "comment pouvez-vous écrire exception code de sécurité":

  • [moi] Un swap ne manquera jamais (ne même pas écrire un lancer swap)
  • [nobar] C'est une bonne recommandation pour les écrits de swap (). Il convient de noter, toutefois, que std::swap() peut échouer en fonction des activités qu'il utilise en interne

la valeur par défaut std::swap vous de faire des copies et des devoirs, qui, pour certains objets, vous pouvez jeter. Ainsi, les swaps sur défaillance de pourrait lancer, soit utilisé pour vos cours, ou même pour les classes de la STL. Aussi loin que la norme C++, le swap de fonctionnement pour vector, deque, et la liste ne sont pas les jeter, alors qu'il pourrait pour la carte si le foncteur de comparaison peuvent jeter sur la copie de la construction (Voir Le Langage de Programmation C++, Édition Spécial, annexe E, P. 4.3.Swap).

En regardant Visual C++ 2008 mise en œuvre du vecteur de l'échange, le vecteur d'échange de ne pas jeter si les deux vecteurs ont la même allocateur (c'est à dire, le cas normal), mais faire des copies si elles ont des allocateurs. Et donc, je suppose qu'ils pourraient jeter dans ce dernier cas.

Ainsi, le texte original tient toujours: Ne même pas écrire un lancer de swap, mais nobar commentaire doit être rappelé: assurez-vous que les objets que vous êtes la permutation n'est pas à jeter swap.

Edit 2011-11-06: article Intéressant

Dave Abrahams, qui nous a donné la base/strong/nothrow garanties, décrit dans un article de son expérience sur la STL exception sûr:

http://www.boost.org/community/exception_safety.html

Regarder le 7ème point (test automatique pour une exception de sécurité), où il s'appuie sur des tests unitaires automatisés pour s'assurer que chaque cas est testé. Je suppose que cette partie est une excellente réponse à l'auteur de la question "Pouvez-vous être sûr que c'est?".

Edit 2013-05-31: Commentaire de dionadar

t.integer += 1; est sans la garantie que le trop-plein n'arrive PAS exception coffre-fort, et, en fait, peut techniquement invoquer UB! (Signé débordement est UB: C++11 5/4 "Si lors de l'évaluation d'une expression, le résultat n'est pas définie mathématiquement ou pas dans la gamme des représentable valeurs pour son type, le comportement est indéfini.") Notez que entier non signé ne débordent pas, mais faire leurs calculs dans une classe d'équivalence modulo 2^#bits.

Dionadar fait référence à la ligne suivante, qui a effectivement un comportement indéterminé.

   t.integer += 1 ;                 // 1. nothrow/nofail

La solution ici est de vérifier si l'entier est déjà à sa valeur max (à l'aide d' std::numeric_limits<T>::max()) avant de faire l'addition.

Mon erreur qui va dans le "Normal d'échec vs bug" de la section, c'est un bug. Il n'a pas d'invalider le raisonnement, et cela ne signifie pas exception coffre-fort à code est inutile car impossible à atteindre. Vous ne pouvez pas vous protéger contre l'ordinateur mise hors tension ou le compilateur de bugs, ou même de vos bugs, ou d'autres erreurs. Vous ne pouvez pas atteindre la perfection, mais vous pouvez essayer d'obtenir aussi près que possible.

J'ai corrigé le code avec Dionadar commentaire à l'esprit.

32voto

Joh Points 1788

L'écriture d'exception-safe code en C++ n'est pas tellement au sujet de l'aide de beaucoup de try { } catch { } blocs. C'est à propos de la documentation de ce type de garanties de votre code fournit.

Je recommande la lecture de Herb Sutter est le Gourou De La Semaine de la série, en particulier versements 59, 60 et 61.

Pour résumer, il y a trois niveaux de l'exception de sécurité vous pouvez fournir:

  • De base: Lorsque votre code déclenche une exception, votre code n'est pas une fuite de ressources, et les objets restent destructibles.
  • Forte: Lorsque votre code déclenche une exception, il quitte l'état de l'application inchangé.
  • Ne jetez: Votre code ne jamais lève des exceptions.

Personnellement, j'ai découvert ces articles assez tard, une grande partie de mon code C++ n'est certainement pas exception-safe.

20voto

bmargulies Points 49855

Certains d'entre nous utilisent exception depuis plus de 20 ans. PL / I a, par exemple. L’hypothèse qu’ils sont une technologie nouvelle et dangereuse paraît discutable pour moi.

18voto

D.Shawley Points 30324

Tout d'abord (comme Neil a déclaré), SEH Microsoft, qui est la gestion Structurée des exceptions. Il est similaire mais pas identique à celle de traitement des exceptions en C++. En fait, vous devez activer la gestion des exceptions C++ si vous le souhaitez dans Visual Studio, le comportement par défaut ne garantit pas que les objets locaux sont détruits dans tous les cas! Dans les deux cas, la gestion des exceptions n'est pas vraiment difficile , il est juste différent.

Maintenant, pour vos questions.

Avez-vous vraiment écrire exception coffre-fort à code?

Oui. Je m'efforce d'exception coffre-fort à code dans tous les cas. Je évangéliser à l'aide de RAII techniques de l'étendue de l'accès aux ressources (par exemple, boost::shared_ptr pour mémoire, boost::lock_guard pour le verrouillage). En général, l'utilisation cohérente de RAII et la portée de gardiennage techniques fera exception coffre-fort à code beaucoup plus facile à écrire. Le truc, c'est de savoir ce qui existe et comment l'appliquer.

Êtes-vous sûr de votre dernière "production ready" code d'exception en sécurité?

Pas de. Il est aussi sûr qu'il est. Je peux dire que je n'ai pas vu un processus de panne en raison d'une exception en plusieurs années de 24/7 de l'activité. Je ne m'attends pas parfait code, il suffit de bien le code écrit. En plus de fournir de l'exception de sécurité, les techniques ci-dessus garantir l'exactitude d'une manière qui est presque impossible à atteindre avec des try/catch blocs. Si vous êtes à la capture de tout ce qui dans votre liste de contrôle de la portée (thread, processus, etc.), ensuite, vous pouvez être sûr que vous allez continuer à s'exécuter en face des exceptions (la plupart du temps). Les mêmes techniques seront également vous aider à continuer à fonctionner correctement dans le visage des exceptions sans try/catch blocs de partout.

Pouvez-vous être sûr que c'est?

Oui. Vous pouvez être sûr par une bonne audit de code, mais personne n'a vraiment fait que font-ils? Régulièrement des examens du code et de l'attention des développeurs aller un long chemin pour y arriver.

Savez-vous et/ou réellement utiliser des alternatives qui fonctionnent?

J'ai essayé un peu de variations au fil des ans, tels que l'encodage des états dans les bits de poids (ala HRESULTs) ou l'horrible setjmp() ... longjmp() hack. Ces deux décomposer dans la pratique, bien que dans de manières complètement différentes.


En fin de compte, si vous prenez l'habitude d'appliquer un peu de techniques et de réfléchir à l'endroit où vous pouvez réellement faire quelque chose en réponse à une exception, vous allez vous retrouver avec très lisible le code qui est exception coffre-fort. Vous pouvez résumer cela en suivant ces règles:

  • Vous voulez voir seulement try/catch quand vous pouvez faire quelque chose à propos d'une exception spécifique
  • Vous avez presque ne veux plus jamais voir un raw new ou delete dans le code
  • Évitent std::sprintf, snprintf, et les matrices d'utilisation générale std::ostringstream pour la mise en forme et de remplacer les tableaux avec des std::vector et std::string
  • En cas de doute, regardez pour les fonctionnalités Boost ou STL avant de rouler votre propre

Je ne peux que recommander que vous apprenez à utiliser les exceptions correctement et d'oublier les codes de résultat si vous prévoyez d'écrire en C++. Si vous voulez éviter les exceptions, vous pouvez envisager d'écrire dans une autre langue qui, soit n'ont pas eux ou de leur sécurité. Si vous voulez vraiment apprendre à tirer pleinement profit de C++, de lire quelques livres de Herb Sutter, Nicolai Josuttis, et Scott Meyers.

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