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:
-
aucun : Votre code ne doit jamais offrir. Ce code est une fuite de tout, et de briser à la première exception levée.
-
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
-
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++)
-
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.