IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C++ Discussion :

Fonctionement de try / catch et throw


Sujet :

C++

  1. #1
    Membre habitué
    Homme Profil pro
    Webmaster
    Inscrit en
    Mars 2008
    Messages
    164
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Webmaster

    Informations forums :
    Inscription : Mars 2008
    Messages : 164
    Points : 154
    Points
    154
    Par défaut Fonctionement de try / catch et throw
    Bonjour,

    je teste le fonctionnement de try / catch et throw.

    Je suis devant une erreur que je ne comprend pas.

    En passant en argument un nombre, le programme imprime la suite des nombre le composant.
    Il y a deux compteurs.

    L'objectif est de vérifier que l'argument est bien un nombre. Si cela n'est pas le cas, try catch et trhrow prend le relais.
    La compilation se passe bien. Mais, j'ai un "segmentation fault" lors des essais.

    C'est ce dernier que je ne comprend pas.

    Voici mon ecm:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
     
    #include <stdio.h> // printf
    #include <string> // stoi
    #include <iostream> // cerr cout
     
    using namespace std;
     
    // Déclaration des fonctions
    int nombreEntier(char *chaine);
     
    // Fonction Principale
    int main(int argc,char *argv[]){
    	int n=0;
    	int i=0;
    	int compte=1;
    	int decompte=0;
     
    	try{
    		n = nombreEntier(argv[1]);
    	}
    	catch(string const& err){
    		cerr << err << endl;
    		return 0;
    	}
    	decompte=n;
    	cout << decompte--;
    	while(i < n){
    		cout << ',' << compte << ',' ; 
    		compte++;
    		cout << compte << ',' << decompte << ',';
    		decompte--;
    		cout << decompte ;
    		i=i+4;
    	}
    	cout << endl;
    	printf("%d", n);
    }
     
    int nombreEntier(char *chaine){
    	int y; // indice
    	int correct=1;
    	while(chaine[y] != '\0'){
    		if(chaine[y] < '0' || chaine[y] > '9') correct = 0;
    		y++;
    	}
    	if(correct == 0) throw string("\nUniquement un nombre entier!\n");
    	else
    		return stoi(chaine);
    }
    Si quelqu'un a une idée, une piste...

    Cordialement

  2. #2
    Expert éminent
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 565
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 565
    Points : 7 648
    Points
    7 648
    Par défaut
    Ligne 40 : y n'est pas initialisé

  3. #3
    Membre habitué
    Homme Profil pro
    Webmaster
    Inscrit en
    Mars 2008
    Messages
    164
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Webmaster

    Informations forums :
    Inscription : Mars 2008
    Messages : 164
    Points : 154
    Points
    154
    Par défaut
    Citation Envoyé par dalfab Voir le message
    Ligne 40 : y n'est pas initialisé
    Comme quoi, pourquoi chercher compliquer?

  4. #4
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    L'option `-Wall` de clang++ le voit ça normalement.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  5. #5
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    Quelques remarques au passage:

    1- On N'UTILISE PAS la directive using namespace std; : cette fonctionnalité a été proposée au tout début de la standardisation, quand il a été décidé de faire passer le contenu de la bibliothèque standard dans l'espace de nom std, pour permettre à la base de code existante (qui utilisait une version de la SL qui se trouvait dans l'espace de noms global) de continuer à compiler avec "un minimum de modifications".

    Mais elle pose énormément de problèmes du simple fait qu'elle "brise" la système d'espaces de noms. Tu as très largement intérêt à prendre l'habitude de préfixer les éléments qui viennent de la SL de std::; après tout, tu ne risque pas vraiment d'user ton clavier en le faisant, et cela ne prend pas tellement de temps de le faire

    2- au lieu de transmettre un char * à ta fonction nombreEntier, transmet une std::string, sous forme de référence constante: std::string est la classe qu'il te faut pour manipuler des chaines de caractères (voir le (4) )

    3- Traditionnellement, le catch prend place à la fin de la fonction, juste avant l'éventuel retour qui pourrait survenir lorsque "tout s'est bien déroulé". Un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    try{
    	n = nombreEntier(argv[1]);
    	decompte=n;
    	cout << decompte--;
    	while(i < n){
    		cout << ',' << compte << ',' ; 
    		compte++;
    		cout << compte << ',' << decompte << ',';
    		decompte--;
    		cout << decompte ;
    		i=i+4;
    	}
    	cout << endl;
    	printf("%d", n);
    }
    catch(string const& err){
    	cerr << err << endl;
    		return 0;
    }
    (je n'ai fait que déplacer une partie de la logique )
    produira exactement le même résultat, mais garde toute la logique "normale" "groupée" car tout ce qui est sensé bien se dérouler se trouve dans le try

    4- Pourquoi utiliser printf La sortie standard en C++, c'est std::cout.

    printf est une fonction issue du C, et, bien que C++ ne renie pas son héritage venu du C, il faut bien comprendre que ce sont deux langages totalement différents.

    5- 0 est la valeur que le système d'exploitation s'attend à recevoir lorsque tout s'est correctement déroulé. Lorsque tu te trouves dans le catch, c'est, justement, que ton application dans laquelle ton application a "rencontré un problème" et n'a pas pu se dérouler correctement.

    En renvoyant 0 au système d'exploitation, tu lui ments... renvoie plutot EXIT_FAILLURE qui correspond à une valeur que le système d'exploitation pourra considérer comme... le fait que "quelque chose a foiré"

    6- la toute dernière ligne de la fonction main devrait toujours être return 0;!

    Après tout, le prototype de cette fonction est int main() ou int main(int argc, char ** argv), et le système d'exploitation utilise la valeur renvoyée pour savoir comment s'est déroulée l'application.

    Si on arrive à la fin de la fonction main, c'est que tout s'est correctement déroulé, et il faut donc tenir le système d'exploitation au courant de ce fait.

    Note que la norme accepte effectivement à titre exceptionnel que la fonction main ne se termine pas par l'instruction return, mais, comme tout compilateur "bien réglé" va te crier dessus si tu oublie le return final d'une fonction qui est sensée renvoyer quelque chose, autant prendre l'habitude de rajouter cette instruction pour chaque fonction susceptible de renvoyer quelque chose, y compris pour la fonction main.

    7- Le type de correct, dans ta fonction nombreEntier, devrait être bool vu que tu ne t'attends qu'à avoir deux valeur possibles : vrai ou faux

    8- Je sais que le code que tu as écrit n'est destiné qu'à te servir d'essai pour l'utilisation de try ... catch, mais l'exemple est vraiment mal choisi dans le cas présent.

    En effet, les exceptions sont destinées à représenter ... des situations exceptionnelles, dont le développeur "ose espérer" qu'elles ne se produiront jamais, sans pour autant pouvoir garantir que ce sera effectivement le cas, parce que les données qu'il manipule viennent "de l'extérieur".

    Or, dans le cas présent, c'est le développeur qui décide des valeurs qui seront transmises comme paramètres à la fonction nombreEntier. Si ces valeurs ne sont pas correctes, nous sommes donc face à une erreur de logique de sa part.

    Cela sous entend que, si une erreur survient, le développeur devra corriger la logique avant de mettre son application en production, et donc que l'erreur ne pourra plus se produire une fois l'application en production.

    En un mot comme en cent, nous sommes face à ce que l'on appelle une précondition en programmation par contrat.

    Et les préconditions sont typiquement le genre de choses qui devraient être vérifiées à l'aide d'assertions, qui pourront être transformées en "no-op" quand l'application passera en production

    9- Quand la fonction main utilise le prototype int main (int argc, char * argv[]), argc correspond au nombre de chaine de caractères que l'on a utilisée pour lancer l'application.

    argv[0] correspondra d'office au nom de l'application (éventuellement avec le chemin absolu qui permet d'y accéder, selon le système d'exploitation) et les paramètres correspondront à argv[1] ... argv[argc-1].

    Tu dois systématiquement t'assurer avant toute chose qu'il aura fourni le nombre de paramètres auquel tu t'attends .

    Si ton application s'appelle myapp et que tu t'attends à recevoir une chaine de caractères comme paramètre, tu dois te dire que l'utilisateur est un imbécile distrait, et que tu dois donc t'attendre aussi bien à ce qu'il essaye de lancer l'application sous la forme de
    que sous la forme de (qui est ce à quoi tu t'attends)
    que sous la forme de
    La première et la troisième forme pour lancer ton application ne peuvent pas fonctionner car elles ne correspondent clairement pas à ce à quoi tu t'attends.

    Pire encore: ton code, tel qu'il se présente (en essayant de transmettre directement argv[1] lors de l'appel à nombreEntier risque de faire planter ton application dans le premier cas.

    Tu dois donc prendre l'habitude de mettre systématiquement en doute tout ce que l'utilisateur pourra introduire comme donnée car, si tu pars du principe que "bah, l'utilisateur sait ce qu'il fait", tu peux te dire qu'il y a neuf chances sur dix qu'il fasse une connerie, en vertu de la loi de finagle

    Par contre, si tu pars du principe que "je m'attends toujours au pire, comme cela je ne serai jamais déçu", et que tu vérifies toujours "plutôt deux fois qu'une" que l'utilisateur n'a pas introduit une connerie, tu pourras affirmer sans crainte que "oui, je suis sur que mon application réagira correctement"

    10- La règle est toujours de déclarer les variables au plus près de leur utilisation. Le code final de ta fonction main devrait donc ressembler à quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    int main(int argc,char *argv[]){
        try{
            if(argc!= 2){
                throw std::string("usage : application_name <some string>");
            }
            int n = nombreEntier(argv[1]);
            int decompte=n;
    	cout << decompte--;
            int i=0;
            int compte = 1;
    	while(i < n){
                cout << ',' << compte << ',' ; 
                compte++;
                cout << compte << ',' << decompte << ',';
                decompte--;
                cout << decompte ;
                i=i+4;
    	}
            cout << endl;
        }
        catch(string const& err){
            cerr << err << endl;
            return 1;
        }
        return 0;
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #6
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 109
    Points
    6 109
    Par défaut
    @koala01 : +1 pour la majorité de ton message, mais :
    Citation Envoyé par koala01 Voir le message
    2- au lieu de transmettre un char * à ta fonction nombreEntier, transmet une std::string, sous forme de référence constante: std::string est la classe qu'il te faut pour manipuler des chaines de caractères (voir le (4) )
    Le type le plus pertinent en paramètre de nombreEntier est std::string_view (passé par valeur), car :
    • Si le paramètre est de type std::string const& et l'argument de type char const*, alors cela force une conversion qui peut coûter une allocation dynamique, sauf en cas de SSO (Small String Optimization).
    • Si le paramètre est de type std::string const& ou char const*, alors on ne peut passer que des chaînes qui se terminent par '\0'.

  7. #7
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    @koala01 : +1 pour la majorité de ton message, mais :

    Le type le plus pertinent en paramètre de nombreEntier est std::string_view (passé par valeur)
    Dans l'absolu, je suis d'accord avec toi. Sauf que std::string_view possède deux problèmes majeurs dans ce cas :
    - std::string_view est relativement récent, tous les compilateurs en service ne l'ont pas forcément
    - std::string_view est plus compliqué à utiliser que std::string car elle a une sémantique de référence, et j'ai déjà vu des développeurs C++, même expérimentés, se casser les dents là dessus. Alors, certes, le gain de perfs en vaut probablement la chandelle, dans du vrai code, mais j'hésiterais à en parler à quelqu'un au début de son apprentissage...
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  8. #8
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    @koala01 : +1 pour la majorité de ton message, mais :

    Le type le plus pertinent en paramètre de nombreEntier est std::string_view (passé par valeur)
    J'eu -- effectivement -- pu parler de std::string_view.

    Mais je rejoins malgré tout l'avis de JolyLoic, car c'est la raison principale pour laquelle je ne l'ai pas fait

    De plus, quand tu dis
    • Si le paramètre est de type std::string const& ou char const*, alors on ne peut passer que des chaînes qui se terminent par '\0'.
    j'ai quand même envie de répondre "raison de plus"!

    Car, si tu regardes attentivement le code fourni par Zuthos, tu remarqueras qu'il utilise une boucle basée sur
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    while(chaine[y] != '\0'){
    Le truc, c'est que je n'oserais jurer que le passage de argv[1] sous forme de const char * fournira d'office le '\0' final.

    Je crois que c'est le cas, car, autrement, nous n'aurions aucun moyen de repérer les différentes chaines de caractères, mais comme j'ai la flegme d'aller vérifier dans la norme C, je préfère "m'attendre au pire pour ne jamais être déçu" et considérer que ca risque de ne pas être le cas
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  9. #9
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Car, si tu regardes attentivement le code fourni par Zuthos, tu remarqueras qu'il utilise une boucle basée sur
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    while(chaine[y] != '\0'){
    Le truc, c'est que je n'oserais jurer que le passage de argv[1] sous forme de const char * fournira d'office le '\0' final.

    Je crois que c'est le cas, car, autrement, nous n'aurions aucun moyen de repérer les différentes chaines de caractères, mais comme j'ai la flegme d'aller vérifier dans la norme C, je préfère "m'attendre au pire pour ne jamais être déçu" et considérer que ca risque de ne pas être le cas
    D'après cppreference.com, on a bien des "null-terminated multibyte strings". Par ailleurs argc[argv] est garanti d'être un pointeur nul.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  10. #10
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par ternel Voir le message
    D'après cppreference.com, on a bien des "null-terminated multibyte strings". Par ailleurs argc[argv] est garanti d'être un pointeur nul.
    Merci d'avoir levé le doute

    Mais, justement, au sujet de la garantie argv[argc] == nullptr :

    Dans le code d'origne, Zuthos ne vérifie pas la valeur de argc dans la fonction main, mais il ne vérifie pas non plus que chaine n'est pas null dans nombreEntier.

    Rien que cela peut mener à une catastrophe, car il essaye systématiquement de déréférencer chaine qui peut -- du coup -- être null.

    l'utilisation de std::string au lieu d'une std::string_view nous permet de profiter avantageusement de la logique interne de std::string, bien que -- je dois le reconnaitre -- cela puisse occasionner des conversions supplémentaire

    En effet, en transmettant une std::string, et en utilisant la fonction membre size, nous pouvons éviter le plantage pur et simple, car l'appel de size() sur une std::string créée avec nullptr lancera un exception de type std::logic_error que l'on pourra rattraper dans un bloc catch.

    Si bien que l'on pourrait même aller jusqu'à conseiller de modifier la fonction nombreEntier pour lui donner une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int nombreEntier(std::string const & chaine){
        for(size_t index=0; index <chaine.size() // lancera l'exception std::logic_error si argc == 1
            index++){
            if(chaine[index]<'0' || chaine[index]>'9'){
                throw std::string{"not a number"};
            }
        }
        return std::stoi(chaine);
    }
    et modifier la fonction main en conséquence en lui donnant une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
     
    int main(int argc, char ** argv){
        try{
            if(argc >2) { // myapp xyz machinbrol ... pas bon, refusé d'office
                throw std::string{"just use myapp xyz please"};
            }
            auto n=nombreEntier{argv[1]} // lance std::logic_error si argc ==1
                                                          // lance std::string si xyz n'est pas un nombre
            int decompte=n;
    	cout << decompte--;
            int i=0;
            int compte = 1;
    	while(i < n){
                cout << ',' << compte << ',' ; 
                compte++;
                cout << compte << ',' << decompte << ',';
                decompte--;
                cout << decompte ;
                i=i+4;
    	}
            std::cout << "\n";
        }
        catch(std::string const & e){     // récupérer les exceptions qui ont la forme de chaines de caractères
            std::cout<<e<<"\n";
            return EXIT_FAILLURE; // y a eu un problème
        }
        catch(std::runtime_error const & e){ // récupérer toutes les exceptions lancée par la bibliothèque standard
           // (j'ai peut être oublié un cas qui en lancerait une :D)
           std::cout<<e.what()<<"\n";
           return EXIT_FAILLURE; // y a eu un problème
        }
        return 0; //tout s'est bien passé
    }
    Je ne dis pas -- loin de là -- que la solution que je présente ici est plus efficace que l'utilisation de std::string_view.

    Je dis juste que c'est une solution qui fonctionne, et qu'elle permet de tirer le "meilleur parti" de la logique interne de std::string afin de gérer les cas "peau de banane"
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  11. #11
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    A vrai dire, je préfèrerai ne pas reposer sur une exception lancée par une fonction membre de std::string, alors que le problème est que je n'ai pas vérifié les données utilisateurs.
    Cela m'éviterai complètement d'écrire un try catch, puisque je sais déjà traiter l'erreur.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
     
    struct not_a_number {};
     
    int nombreEntier(std::string const & chaine){
        for(size_t index=0; index <chaine.size() // lancera l'exception std::logic_error si argc == 1
            index++){
            if(chaine[index]<'0' || chaine[index]>'9'){
                throw not_a_number();
            }
        }
        return std::stoi(chaine);
    }
     
    int main(int argc, char ** argv) {
        if (argc != 2) {
            std::cerr << "usage : myapp <number>." << std::endl;
            return EXIT_FAILLURE;
        }
     
        try{
            auto n=nombreEntier{argv[1]};
            int decompte=n;
    	cout << decompte--;
            int i=0;
            int compte = 1;
    	while(i < n){
                cout << ',' << compte << ',' ; 
                compte++;
                cout << compte << ',' << decompte << ',';
                decompte--;
                cout << decompte ;
                i=i+4;
    	}
            std::cout << "\n";
        }
        catch(not_a_number const & e) {
           std::cout << "your argument is not a number" << endl;
           return EXIT_FAILLURE;
        }
     
        return 0;
    }
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  12. #12
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Attention, je reconnais volontiers que ce n'est pas l'idéal...

    Ne serait-ce que parce que la validité de la chaine de caractères (quelle qu'en soit la forme) attendue par la fonction nombreEntier représente une précondition, alors que le fait d'avoir "autre chose" que deux arguments saisis représente une situation "dont on espère qu'elle ne se produira pas, sans pouvoir l'empêcher pour la cause"

    De manière générale, la fonction main devrait donc lancer une exception (programmation défensive) si argc != 2 et la fonction nombreEntier devrait faire une assertion sur la validité de la chaîne qui lui est transmise (programmation par contrat)

    En outre, la gestion de toutes les ecxeptions -- quelles qu'elle soient -- occasionnées par "une incohérence quelconque" au niveau des arguments transmis lors du lancement du programme devra forcément remonter jusqu'à la fonction main, et pourraient même remonter jusqu'au système d'exploitation

    D'un autre coté, il faut avouer que l'exemple choisi par Zuthos n'est pas non plus le mieux adapté à la compréhension des exceptions, car, si le fait que la chaine de caractères qui est transmise à nombreEntier est une précondition, le fait que cette chaine de caractères corresponde effectivement à un nombre entier devrait aussi être considéré comme une précondition

    Mais le fait est que la fonction nombreEntier d'origine (et toutes les variations qu'on a pu proposer par la suite) prend en réalité deux responsabilités:
    1. valider les entrées de l'utilisateur ET
    2. extraire le nombre entier indiqué

    Et ca, c'est contraire au SRP...

    Si bien que "dans l'idéal", il faudrait en réalité une fonction checkArg en remplacement de la fonction nombreEntier, qui serait proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    void checkArg(char * arg){
        if(!arg)
            throw std::string("please use myapp xyz");
        while(arg){
            if(*arg<'0' || *arg>'9')
                throw std::string("not a number");
        }
    }
    en plus du fait que main devrait pouvoir lancer une exception si argc > 2 et que la fonction main utilise "simplement" std::stoi pour faire la conversion, sous une forme qui serait dés lors proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    int main(int argc, char ** argv){
       try{ // on n'est pas sur de ce que l'utilisateur aura introduit
           if(argc>2){   // on est sur que l'utilisateur a fait une couille
               throw std::string("too many arguments");
           }
           checkArg(argv[1]); // les autres vérifications
           int n=std::stoi(std::string(argv[1]);
           /* la suite */
        }
        catch(std::string const & e){ // s'il y a eu une couille, on la traite ici
            std::cout<<e<<"\n";
            throw e; // autant relancer l'exception pour mettre fin à l'application
            /* OU   OU  OU
            exit(EXIT_FAILLURE);
            */
        }
        /* tout s'est bien passé */
        return 0;
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  13. #13
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2017
    Messages
    34
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Israël

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Enseignement

    Informations forums :
    Inscription : Octobre 2017
    Messages : 34
    Points : 14
    Points
    14
    Par défaut
    Citation Envoyé par koala01 Voir le message

    1- On N'UTILISE PAS la directive using namespace std; : cette fonctionnalité a été proposée au tout début de la standardisation, quand il a été décidé de faire passer le contenu de la bibliothèque standard dans l'espace de nom std, pour permettre à la base de code existante (qui utilisait une version de la SL qui se trouvait dans l'espace de noms global) de continuer à compiler avec "un minimum de modifications".

    Mais elle pose énormément de problèmes du simple fait qu'elle "brise" la système d'espaces de noms. Tu as très largement intérêt à prendre l'habitude de préfixer les éléments qui viennent de la SL de std::; après tout, tu ne risque pas vraiment d'user ton clavier en le faisant, et cela ne prend pas tellement de temps de le faire
    Excusez moi,mais je ne saisis pas bien le problème de l'utilisation de using namespace std;pouriez vous m'aider à le comprendre ? Qu'est ce que "briser" le système d'espace de noms ? Quel est le risque ou bien l'inconvénient ?

    Merci

  14. #14
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Legro Voir le message
    Excusez moi,mais je ne saisis pas bien le problème de l'utilisation de using namespace std;pouriez vous m'aider à le comprendre ? Qu'est ce que "briser" le système d'espace de noms ? Quel est le risque ou bien l'inconvénient ?

    Merci
    Bon, cette réponse va encore me faire écrire un véritable roman... Accroche toi

    Car, pour comprendre le problème, il faut déjà comprendre ce qu'est un espace de noms. Nous allons donc commencer par là

    Nous pourrions comparer les espaces de noms à des boites de rangements, dans lesquelles tu pourrais placer les choses "qui vont bien ensembles".

    Tu pourrais donc avoir une boite estampillée (un espace de noms nommé) "outils" dans laquelle tu rangerais tous tes outils et un(e) autre nommé "cuisine" dans laquelle tu rangerais toutes tes affaires de cuisines.

    La seule chose, c'est que tu peux aussi décider de mettre une ou plusieurs "boites plus petites" à coté des éléments d'ordre "plus généraux". Par exemple, tu pourrais avoir ton marteau et tes tournevis dans la boite "outils", mais avoir une boite plus petite qui contiendrait les outils destinés exclusivement au jardinage, une autre qui contiendrait les outils destinés exclusivement à l'électricité, et une troisième qui contiendrait les outils destinés exclusivement la mécanique.

    C'est très cool, car tu sais que, si tu as besoin d'un outil, tu le trouvera dans la boite "outils" et non dans la boite "cuisine", et que, de même, si tu as besoin d'un râteau, tu le trouveras dans la boite "jardin" qui se trouve dans la boite "outils" et non dans la boite "mécanique" qui se trouve aussi dans la boite "outils".

    Quand je dis que la directive using namespace XXX; "brise le système d'espace de noms", c'est parce que cela revient strictement au même que de ... renverser l'ensemble de la boite dans laquelle tu as "si bien tout rangé" sur une table.

    Quant tu ne le fais qu'avec la boite "outils", ce "n'est pas si grave", parce que les boîtes "jardin","électricité" et "mécanique" sont "bien fermées (et ne risquent donc pas de se vider par la même occasion), mais essaye d'imaginer le résultat si tu décidais de renverser -- sur la même table -- le contenu de la boite "cuisine"... :

    tu retrouverais ton marteau et tes tournevis entourés de louches, de couteaux à découper et de lèches-plats... Outre le fait que ce ne serait pas très hygiénique d'utiliser un couteau qui vient de se frotter au manche plein de graisse d'un tournevis, tu pourrais très facilement en venir à te blesser sur un de tes couteaux en voulant accéder à un des outils dont tu as besoin.

    Bref, tu te rendras compte que ce ne serait vraiment pas une bonne idée

    Les choses sont encore pire avec les espaces de noms, car, comme je te l'ai dit plus haut, il permettent de ranger les "choses qui vont bien ensembles".

    Sauf que le terme utilisé pour désigner "quelque chose" est toujours choisi de manière à faire comprendre à celui qui lit le code l'usage auquel ce "quelque chose" sera destiné.

    Si bien que le même terme a de très fortes chances d'être utilisé dans différents contextes, qui n'ont pas forcément de liens entre les deux, et pour lesquels le seul moyen d'éviter d'avoir des conflits est ... de ranger correctement ces termes dans un espace de noms.

    Prenons un exemple classique :

    Tu crées une bibliothèque qui expose une fonction foo(), et, comme tu es un développeur consciencieux, tu va la placer dans ... l'espace de noms "général" de ta bibliothèque sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    namespace TaBibliothèque{
        void foo();
    }
    C'est cool, parce qu'elle est "bien rangée"

    De mon coté, je crée une autre bibliothèque, qui n'a absolument rien à voir avec la tienne, mais qui expose aussi une fonction foo, et, comme je suis un développeur consciencieux, vais la placer dans ... l'espace de noms "général" de ma bibliothèque sous une forme proche
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    namespace AutreBibliothèque{
        void foo();
    }
    C'est cool, parce qu'elle est "tout aussi bien rangée" que la tienne

    Voilà que Jules (Henry, Arthur... qui tu veux) décide de développer un projet qui utilise, pour certains aspects de son projet, ta bibliothèque et pour d'autres aspects de son projet ma bibliothèque.

    Telle que nous avons prévu les chose, il n'y a aucun conflit possible, car, pour faire appel à ta fonction foo, il devra "ouvrir la boite" de ta bibliothèque, et il y fera donc appel sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    TaBibliothèque::foo();
    alors que pour faire appel à ma fonction foo, il devra ouvrir la boite de ma bibliothèque, et il y fera donc appel sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    AutreBibliothèque::foo();
    Et même si il veut faire appel aux deux fonctions l'une après l'autre, il n'aura absolument aucun problème.

    Sauf que Jules, il est fainéant... Et ca le fait ch**er de devoir écrire à chaque fois TaBibliothèque:: ou AutreBibliothèque::.

    Du coup, il va prendre l'habitude, lorsqu'il utilise ta bibliothèque, d'utiliser la directive
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    using namespace TaBibliothèque;
    , et, lorsqu'il va travailler avec ma bibliothèque, d'utiliser la directive
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    using namespace AutreBibliothèque;
    Et tout ira bien "le plus souvent". Jusqu'au jour où il voudra utiliser nos deux bibliothèques en même temps.

    Parce que ce jour là, il va faire "comme il fait d'habitude", à savoir écrire un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    using namespace TaBibliothèque; // pour pouvoir utiliser ta bibliothèque
    namespace AutreBibliothèque; // pour pouvoir utiliser ma bibliothèque
    void bar(){ // la fonction qu'il veut développer
        foo(); // OUCH!!!
        foo(); // RE-OUCH!!!
    }
    Car le compilateur va lui dire (deux fois, qui plus est) qu'il y a conflit : il est totalement incapable de déterminer "quelle version" de foo() il doit appeler:
    • doit-il appeler d'abord la version de ta bibliothèque puis la version de la mienne
    • doit-il faire l'inverse
    • doit-il appeler deux fois la version de ta bibliothèque
    • doit-il appeler deux fois la version de la mienne

    Comment veux tu qu'il puisse le savoir !!!

    Pire encore! imaginons un programme qui calcule le prix tva comprise sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <iostream>
     
    int main(int argc, char** argv){
      int prix_ht = 42;
      float tva = 0.2;
     
      int cout = prix_ht * (1. + tva);
     
      std::cout<<cout<<std::endl; // ???
    }
    std::cout est -- très clairement la sortie standard, et cout est -- tout aussi clairement -- le "cout tva comprise". Cool!!! il n'y a aucun conflit possible dans ce cas là.

    mais, voyons ce que cela donnerait avec la directive using namespace:

    Tu aurais donc un code qui ressemblerait )
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <iostream>
     
    using namespace std;
     
    int main(int argc, char** argv){
      int prix_ht = 42;
      float tva = 0.2;
     
      int cout = prix_ht * (1. + tva);
     
      cout<<cout<<endl; // ???
    }
    et quand tu vas essayer de le compiler, tu obteindra un message proche d
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     g++ main.cpp 
    main.cpp: In function ‘int main(int, char**)’:
    main.cpp:11:13: error: invalid operands of types ‘intand ‘<unresolved overloaded function type>’ to binary ‘operator<<’
       cout<<cout<<endl; // ???
       ~~~~~~~~~~^~~~~~
    WTF??? "cout est de type 'int' et tu lui donnes des paramètres qui conviennent pas". De quoi tomber chèvre.

    Tout cela parce qu'il faut savoir que l'opérateur << existe bel et bien pour les données de type int, mais qu'il a un comportement totalement (et qu'il attend un paramètre) différent de l'opérateur << de std::cout :

    Ils portent le même noms (operator <<), mais ils n'ont absolument pas la même fonction:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    int & opertor << (int & a, int b);
    va effectuer un décallage de b bits de vers la gauche de la valeur (binaire) de a alors que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::ostream & opertor<<(std::ostream & ofs, int a);
    va 'injecter" la valeur de a dans le flux ofs.

    Et, enfin, endl est la donnée qui, injectée dans un flux de sortie, provoquera le retour à la ligne et le "flush" du flux de sortie. Mais il n'y a -- et c'est une chance !!! -- aucun moyen de transformer cette donnée en valeur numérique entière.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  15. #15
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2017
    Messages
    34
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Israël

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Enseignement

    Informations forums :
    Inscription : Octobre 2017
    Messages : 34
    Points : 14
    Points
    14
    Par défaut
    Merci Koala ! Toujours clair et concentré sur le sujet traité ! Bravo !

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Try, catch dans une fonction
    Par mactwist69 dans le forum Windows Forms
    Réponses: 10
    Dernier message: 10/07/2008, 16h39
  2. Try/ catch dans une fonction ActionPerformed
    Par thomas2929 dans le forum Langage
    Réponses: 13
    Dernier message: 09/06/2008, 12h06
  3. Fonction du composant FTP -> Try Catch
    Par kilian dans le forum C++Builder
    Réponses: 4
    Dernier message: 04/01/2007, 10h21
  4. Réponses: 3
    Dernier message: 13/12/2006, 16h01

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo