L'aternative habituelle à try/catch/throw est de retourner un code d'erreur (parfois appelé error code que l'appelant teste de manière explicite par quelque condition tel que if. Par exemple, printf(), scanf(), et malloc() fonctionnent de cette façon: l'appeleur doit tester la valeur de retour pour s'assurer que l'appel n'a pas échoué.
Bien que le code d'erreur soit parfois la technique de gestion d'erreur la plus appropriée, il y a quelques désagréable effets de bords à l'ajout de blocs conditionels if non-nécessaires:
Donc, comparé au diagnostic par code d'erreur et if, utiliser try/catch/throw conduira vraissemblablement à un code avec moins de bugs, moins cher à développer, et plus rapide à livrer. Bien sur, si votre organsation n'a pas d'experience avec try/catch/throw , c'est une bonne idée de s'entraîner d'abord sur un mini-projet - on devrait toujours s'habituer à une arme à courte portée avant de l'apporter au front.
[ Haut | Bas | Rechercher ]
Les constructeurs n'ont pas de type de retour, ainsi il n'est pas possible d'utiliser des codes d'erreur. La meilleure façon de signaler la panne de constructeur est donc de jeter une exception.
Si vous n'avez pas ou n'utiliserez pas les exceptions, voici ce que vous pouvez faire. Si un constructeur échoue, le constructeur peut mettre l'objet dans un état "zombie". Faites ceci en plaçant un bit d'état interne de sorte l'objet semble mort bien qu'il soit techniquement encore vivant. Ajoutez alors une fonction membre ("inspecteur" pour contrôler ce bit "zombie" de manière à ce que les utilisateurs de votre classe puissent tester si leur objet est vraiment vivant, ou si c'est un zombie (c.-à-d., un objet "mort-vivant". Egalement vous vérifirez probablement le bit zombi dans vos autres fonctions membre et, si l'objet n'est pas vraiment vivant, rendez le inactif (ou peut-être quelque chose de plus désagréable comme abort(). C'est vraiment laid, mais c'est le mieux que vous puissiez faire si vous ne pouvez pas (ou ne voulez pas utiliser les exceptions.
[ Haut | Bas | Rechercher ]
Voici pourquoi (attachez vos ceintures):
La règle de C++ est que vous ne devez jamais jeter d'exception depuis un destructeur qui est appelé lors du processus connu sous le nom de stack unwinding (déroulage de la pile) après qu'une autre exception ait été jetée. Par exemple, si quelqu'un execute throw Foo(), la pile doit être déroulée de façon à ce que tous les environment d'appel de fonction entre throw Foo() et } catch(const Foo& e) { soient nettoyés et retirés. C'est ce qui s'appelle dérouler la pile.
Lorsque la pile est déroulée, les objets locaux aux environments d'appel de fonction sont détruits (variables déclarées localement dans les fonctions. Si l'un de ces destructeurs jette une exception (supposons qu'il lance un objet Bar), le système se trouverait dans une situation qui lui serait de toutes façon dommageable: devrait il ignorer the Foo et terminer dans le bloc } catch(const Foo& e) { ou il se destinait initiallement ? ou devrait il ignorer le Foo et continuer de chercher un } catch (const Bar& e) { ? Il n'y a pas de bonne réponse -- dans les deux cas de de l'information est perdue.
C'est pourquoi le language C++ garantie d'appeler la fonction terminate() dans une telle situation, et terminate() tue le processus. Bang ! vous êtes mort.
Le moyen simple d'éviter ceci est de ne jamais lancer une exception depuis un destructeur. Mais si vous voulez être malin, vous pouvez ne jamais lancer une exception depuis un destructeur alors que vous traitez une autre exception. Mais dans le second cas, vous êtes dans une situation difficile: le destructeur lui même a besoin de code pour gérer les deux cas: lancer une exception, et faire "quelque chose d'autre", et l'appelant n'a pas de garantie de ce que va faire le destructeur (il peut lancer une exception ou bien faire "quelque chose d'autre"). Donc la solution dans l'ensemble est plus difficile à écrire correctement. Donc la chose simple à faire est de toujours faire "quelque chose d'autre". C'est à dire, ne jamais jeter une exception depuis un destructeur.
Bien sûr, le mot jamais devrait être entre guillemets puisqu'il existe toujours quelques exceptions à la règle. Mais certainement au moins dans 99% des cas, c'est une bonne règle.
[ Haut | Bas | Rechercher ]
Si un constructeur jette une exception, le destructeur de l'objet n'est pas exécuté. Si votre objet a déjà fait une chose qui doit être défaite (comme allouer un bloc de mémoire, ouvrir un fichier, ou verrouiller un sémaphore, ce "truc qui doit être défait" devrait) être gérer par un membre de donnée à l'intérieur de la classe.
Par exemple, plutôt que d'assigner la mémoire dans un membre de donnée nu Fred *, affectez la mémoire allouée à une donnée membre de type "pointeur inteligent" responsable de restituer la mémoire lorsqu'il n'est plus utiliser. Ceci peut être fait automatiquement en appellant delete pour l'object de type Fred dans le destructeur du pointeur inteligent. La classe standard auto_ptr est un exemple de "pointeur inteligent". Vous pouvez également _LINK(freestore-mgmt.html#faq-16.20] ,écrire votre propre compte de référence de pointeur inteligent. Vous pouvez également _LINK(operator-overloading.html#faq-13.3] ,employer les pointeurs inteligents "pour pointer" les enregistrements ou les élements sur le disque d'autres machines
.[ Haut | Bas | Rechercher ]
Par exemple, supposez que vous voulez obtenir une copie d'une chaîne de caractères, jouer avec la copie, ajouter alors une autre chaîne de caractères à l'extrémité de la copie avec laquelle vous avez joué. L'approche tableau de char ressemblerait à quelque chose comme ceci: void userCode(const char* s1, const char* s2) { // Cree une copie de s1: char* copy = new char[strlen(s1) + 1]; strcpy(copy, s1); // Maintenant que nous avons un pointeur local sur de la memoire // allouer sur le tas, il faut utiliser un bloc try { } pour // empecher les fuites de memoire: try { // ... la on joue avec la copie un moment ... // Ajoute s2 a la fin de la copie: // ... Voila ou on veut generalement re-allouer de la memoire ... char* copy2 = new char[strlen(copy) + strlen(s2) + 1]; strcpy(copy2, copy); strcpy(copy2 + strlen(copy), s2); delete[] copy; copy = copy2; // ... on joue encore un petit peu ... } catch (...) { delete[] copy; // Previent les fuites de memoire si on a une exception throw; // Relance l'exception courante } delete[] copy; // Previent les fuites de memoire si on N'a PAS d'exception }
L'utilisation de char *s de cette manière est pénible et sujette à erreurs. Pourquoi ne pas utiliser simplement un objet de classe chaine_de_caracteres ? Votre compilateur fournit probablement a la classe standard string, et elle est probablement aussi rapide et certainement beaucoup plus simple et plus sûr que le code à base de char * que vous vouliez écrire. Par exemple, si vous utilisez la classe string du _LINK(big-picture.html#faq-6.12] , comité de standardisation, votre code pourrait ressembler à quelque chose comme ça:
#include <string> // Laisse le compilateur voir la class std::string void userCode(const std::string& s1, const std::string& s2) { std::string copy = s1; // Fait une copie de s1 // ... la on joue avec la copie un moment ... copy += s2; // Ajoute s2 a la fin de la copie // ... on joue encore un petit peu ... }C'est un total de deux (2) lignes de code dans le corps de la fonction, à comparer avec les douze (12) lignes de code de l'exemple précédent. La plus grande partie de l'économie vient de la gestion de la mémoire, mais aussi parce que nous n'avons plus besoin d'appeler les fonctions du type strxxx(). Voici quelques points importants:
[ Haut | Bas | Rechercher ]
Ecrire à l'auteur,
au traducteur,
ou en savoir plus sur la traduction.
C++ FAQ Lite fr |
Table des matières |
Index |
A propos de l'auteur |
© |
Téléchargez votre propre copie ]
Dernière révision Sun Apr 13 23:54:14 PDT 2003