Partage
  • Partager sur Facebook
  • Partager sur Twitter

étude de noexcept sur exemples

C++

Sujet résolu
    2 février 2019 à 22:04:06

    Je reformule : je me propose un autre exercice pour comprendre.

    Je suppose que dans cette portion de code :

    void var() { // exception neutral
        try {
            foo();
        }
        catch(std::bad_alloc const&) {
            do_something();
            throw;
        }
    }

    do_something() n'est pas no except et renvoie donc une exception par exemple. Est-ce que elle va d'abord être envoyée à var() pour voir si le catch que l'on y a défini peut traiter l'exception ?

    Et si elle ne peut pas, alors on continuera de dépiler ?

    Merci par avance

    -
    Edité par pseudo-simple 2 février 2019 à 22:59:49

    • Partager sur Facebook
    • Partager sur Twitter
      2 février 2019 à 23:38:47

      C'est toi qui essaie de comprendre les exceptions et les garanties, donc ca serait a toi de faire les exercices.

      Ecris les codes et fais tes tests.

      • Partager sur Facebook
      • Partager sur Twitter
        3 février 2019 à 0:39:26

        #include <iostream>
        //#include <utility>
        #include <vector>
        
        void foo(){
        
                while (true) {
                    new int[100000000ul];
                }
        }
        void do_something(){throw 24;}
        
        
        void var() { // exception neutral
            try {
                foo();
            }
            catch(std::bad_alloc const&) {
                do_something();
                throw;
            }
        }



        J'ai codé le code suivant  en lançant une exception dans do_something() pour tester. POur la fonction qui renvoie une exception bad alloc, j'ai utilisé la documentation pour être sûr de renvoyer une std::bad_alloc qui sera récupéré dans le catch :

        https://en.cppreference.com/w/cpp/memory/new/bad_alloc

        Donc, comme prévu do_something() est appelé et j'obtiens le message :

        terminate called after throwing an instance of 'int'

        Je voudrais vous demander quel est le moyen qui permet de tracer une exception justement lors de l'exécution ou de la compilation (je ne sais pas si ça  a du sens de dire cela) ?

        Je pense que j'avais juste au départ puisque effectivement l'exception de type int a probalement dû être renvoyé à la fonction var() qui elle-même n'a pas d'appelant.

        Comment peut-on en avoir le coeur net en traçant l'exception de type int par exemple ?

        Merci

        -
        Edité par pseudo-simple 3 février 2019 à 0:40:11

        • Partager sur Facebook
        • Partager sur Twitter
          3 février 2019 à 3:32:14

          Debugger.

          Mais là, tu ne testes pas l'exception neutrality. Relis les explications précédentes sur à quoi cela sert.

          • Partager sur Facebook
          • Partager sur Twitter
            3 février 2019 à 9:19:13

            Bonjour MOnsieur,

            pour l'exercice de exception neutrality sur lequel vous proposez de focaliser notre attention, la question est , d'après ma compréhension :

            vérifier que l'exception transmise par foo(), avec le bout de code que j'ai ajouté à votre trame de code, n'est pas modifiée. C'est donc après le throw qu'on la récupère. On dépile, et l'exception est transmise à var(), qui elle-même la transmet à main()

            Déjà, ai-je bien formulé la chose à résoudre ?

            Si oui, le code suivant est-il correct ? 

            #include <iostream>
            //#include <utility>
            #include <vector>
            
            
            void foo(){
            
                    while (true) {
                        new int[100000000ul];
                    }
            }
               
            
            void do_something(){}
            
            
            void var() { // exception neutral
                try {
                    foo();
                }
                catch(std::bad_alloc const& e) {
                    std::cout << "Allocation failed: " << e.what() << '\n';
                    do_something();
                    std::cout << "Allocation failed: " << e.what() << '\n';
                    throw;
                }
            }
            
            int main(){
                var();
            }
             

            sachant qu'il y a le const dans la signature du catch qui assure donc que l'exception ne peut pas être modifiée ?

            Par contre, pour l'exercice, je souhaiterais pouvoir afficher l'exception à chaque étape pour bien voir qu'elle n'est pas modifiée, car ce serait formateur.

            J'ai pu l'afficher à l'intérieur du catch. Par contre, je ne sais pas comment l'afficher en sortie de var() et dans le main().

            Note : pour que ça corresponde,  il faut de même que do_something() ne lance pas d'exception.

            -
            Edité par pseudo-simple 3 février 2019 à 11:56:48

            • Partager sur Facebook
            • Partager sur Twitter
              4 février 2019 à 11:08:30

              La manière dont est implémenté les exceptions est fonction, au minimum, du CPU qui exécute le programme, le système d'exploitation, le compilateur utilisé, les options de compilations utilisés, les outils système de monitoring.

              Et je pense que j'ai largement sous-estimé les facteurs de différentiation de l'implémentation de l'implémentation des exceptions en C++.

              Vous essayez de deviner comment c'est implémenté, mais vous êtes à des années lumières d'une implémentation possible car vous ne maitriser déjà assez mal le concept et ses limitations.

              Les fonctions ne "transmettent" pas et ne "reçoivent" pas des exceptions. D'un point de vue implémentation, c'est même pas des objets (au sens Smaltalk ou Objective C avec votre notion de "message" qui n'existe même pas en C++).

              D'un point de vue mécanisme de dépilement lors d'exceptions, les fonctions n'ont aucune réalité dans la majorité des implémentations.

              Pour faire du bricolage sur l'implémentation des exceptions la visualisation de celles-ci, faut utiliser des mécanismes spécifiques des compilateurs.

              Pour VS :

              https://docs.microsoft.com/en-us/cpp/cpp/writing-an-exception-filter?view=vs-2017

              • Partager sur Facebook
              • Partager sur Twitter
              Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                4 février 2019 à 11:38:22

                gbdivers a écrit:

                koala01 a écrit:

                On va faire simple : toute fonction qui n'est pas explicitement noexcept est une fonction qui fait preuve de neutralité vis à vis des exceptions.

                koala01 a écrit:

                Il n'y a que les fonctions explicitement noexcept pour lesquelles le compilateur ne permettra pas à l'exception de remonter.

                Un peu trop simplifié. La notion d'exception-neutral date d'avant le C++11 et noexcept.

                Une fonction peut ne pas être noexcept et ne pas être exception-neutral pour autant. Un simple try-catch peut modifier l'exception et rendre une fonction non neutre.

                void bar() {
                    try {
                        foo();
                    }
                    catch(std::bad_alloc const&) {
                        throw std::runtime_error("error");
                    }
                }

                Mais la présence d'un try-catch n'est pas suffisant pour dire qu'une fonction n'est pas neutre. 

                void var() { // exception neutral
                    try {
                        foo();
                    }
                    catch(std::bad_alloc const&) {
                        do_something();
                        throw;
                    }
                }

                Donc :

                - pas de try-catch du tout = toujours exception neutral

                - noexcept = jamais exception neutral

                - présence d'un try-catch = ça dépend

                @YES, man

                L'exception-neutrality vient des problématiques de exception garantee, c'est à dire les garanties que l'on a quand on utilise les exceptions. (Je me demande si koala ou lmghs n'ont pas écrit un article sur ces garanties ?).

                Quelle est la problématique ? Si tu as un code de gestion des exceptions, par exemple :

                void bar() {
                    try {
                        do_something();
                    }
                    catch(std::bad_alloc const&) {
                        // gestion de l'exception
                    }
                }

                Alors, avec une fonction exception neutral "foo", tu peux écrire :

                void bar() {
                    try {
                        foo(do_something());
                    }
                    catch(std::bad_alloc const&) {
                        // gestion de l'exception
                    }
                }

                et ta gestion des exceptions reste valide.

                Avec une fonction non exception neutral, ta gestion d'exception n'est plus valide.

                Il y a beaucoup à dire sur les garanties que l'on peut apporter sur les exceptions. Je te conseille de te renseigner sur le sujet. Mais dans certains cas (en particulier les classes "utilitaires" de la lib standard comme std::vector, std::unique_ptr, etc), il est très important que les fonctions (membres) soient exception neutral. Cela permet de substituer un code par un autre. Par exemple, imagines que tu veux remplacer un pointeur nu par un pointeur intelligent :

                void foo()
                    UneClasse* p = new UneClasse;
                }

                par :

                void foo()
                    auto p = std::make_unique<UneClasse>();
                }

                Si std::unique_ptr n'est pas exception neutral, alors cela veut dire que ce changement nécessiterait de devoir changer toute la gestion des exceptions. Avec l'exception neutral, on peut faire ce changement sans problème.

                -
                Edité par gbdivers 2 février 2019 à 18:11:33

                Ceci étant dit, la notion de exception neutral avait sans doute du sens avant C++11, mais, pour C++11, je me demande si on ne devrait pas soit revoir les restrictions imposées à cette notion, soit envisager d'utiliser une notion proche de exception aware ou quelque chose du genre. Je m'explique:

                Avant C++11, la situation se résumait en trois point:

                1- si une liste d'exception susceptibles d'être lancées est fournie, la fonction ne peut transmettre que les exceptions indiquées:

                void doSomething() throw(truc, machin){
                    if(condition)
                        throw machin; // OK
                    someFunction(); OK only if trhow truc or machin
                    otherFunction(); // may result in unexpected exception
                                     // if throw something else than truc or machin
                                     // (or them derived)
                }

                2- si aucune indication était donnée, la fonction pouvait transmettre n'importe quelle exception;

                void doSomething() throw(truc, machin);
                void foo(){
                    bar(); // maybe trhow "something"
                    doSomething(); // may only throw truc or machin
                }

                3 - Cas partiulier du (1), une liste d'exception vide : la fonction ne peut transmettre aucune exception:

                void brol() throw(){
                   /* no called function can transmit an exception:
                    * if it does, result in a unexpected exception
                }

                Il y avait donc un sérieux problème si une fonction récupérait une exception pour en lancer une autre, car cela permettait de "contourner" les restrictions imposées:

                void foo() throw(truc){
                   bar(); // OK only if throw truc
                   if(condition)
                       throw truc; // OK
                }
                void someFuncton() throw(machin){
                    // OOOUPSS : foo cannot transmit machin...
                    foo(); // OUUPS : someFunction cannot transmit truc
                           // result in an unexpected exception if bar()
                           // (called by foo) throws
                 }
                void other() throw(machin){
                    try{
                        foo()
                    }
                    catch(truc & e){
                        /* some pre treatment */
                        throw machin; // OK
                    }
                }

                Depuis C++11 et noexcept, les choses sont quand même beaucoup plus simples, car soit une fonction peut lancer / transmettre n'importe quel type d'exception, soit elle ne peut pas le faire.

                Le changement d'exception ne pose donc plus aucun problème, et peut même se justifier, dans une certaine mesure, lorsque plusieurs types d'exceptions sont susceptibles d'être lancés (sans doute par des fonctions différentes) mais nécessitent un traitement identique pour arriver à corriger l'erreur:

                void readInteger(/* ... */){
                    /* ... */
                    if(condition)
                       throw NotAnInteger;
                }
                void readDouble(/* ... */){
                    /* ... */
                    if(condition)
                       throw NotADouble;
                }
                void readString(/* ... */){
                    /* ... */
                    if(condition)
                       throw NotAString;
                }

                Ces trois foncitions pourraient très bien servir à la lecture d'une information d'un type spécifique dans un fichier, par exemple.  Les exceptions que chaque fonction est susceptible de lancer correspondent au problème réellement rencontré, qui peut être généralisé sous la forme de "le format du fichier ne correspond pas à ce que j'attends"

                Nous pourrions donc parfaitement envisager de faire appel à ces fonctions sous une forme proche de

                void foo(){
                    try{
                        if(conditon)
                            readInteger(/* ... */);
                        else if(other_condition)
                            readDouble(/* ... */);
                        else
                            readString(/* ... */);  
                    }
                    catch(NotAnInteger & e){
                        std::string error{"Expected an integer"};
                         throw BadFileFormat(error);
                    }
                    catch(NotADouble & e){
                        std::string error{"Expected a double"};
                         throw BadFileFormat(error);
                    }
                    catch(NotASring & e){
                        std::string error{"Expected a string"};
                         throw BadFileFormat(error);
                    }
                }

                ce qui nous donnerait, au niveau de la pile d'appels qui mène à foo, la possibilité de réagir (d'une manière ou d'une autre) à une situation sommes toutes identique : le format du fichier est incorrect.

                Dans le pire des cas, les fonctions qui mènent à l'appel de foo() vont essayer de récupérer les exception NotAnInteger, NotADouble et NotAString, et non l'exception BadFileFormat

                Et, forcément, elles ne les récupéreront pas, vu que la seule exception transmise est BadFileFormat, et qu'aucune des trois autres ne remonte "aussi haut" dans la pile d'applels; ce qui se transformera, lorsque l'on aura remonté jusqu'à main() en ... UncaughtException.  Mais ca reste, malgré tout, un ... traitement approprié du problème :p

                Quoi qu'il en soit, la raison pour laquelle nous pourrions décider de changer le type de l'exception est tout autre: Avant C++11, il y avait "une bonne" raison de le faire (permettre la généralisation du traitement de différents types d'exceptions) et "une mauvaise" raison de le faire (satisfaire aux restrictions imposées sur les  exceptions susceptibles d'être lancées ou transmises par les fonctions).

                A l'heure actuelle, il n"y a plus que "la bonne raison" qui puisse nous inciter à changer le type d'une exception en cours de traitement ;)

                -
                Edité par koala01 4 février 2019 à 11:39:13

                • Partager sur Facebook
                • Partager sur Twitter
                Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                  4 février 2019 à 22:24:16

                  Tu inverses la problématique de la garantie apportée par l'exception neutrality.

                  Le but est de garantir que l'utilisateur d'une lib n'a pas besoin de changer son code s'il utilise une fonction exception neutral.

                  Ce dont tu parles, c'est a dire des problématiques liées aux fonctions non exception neutral, sont intéressante, mais elles ne se posent justement que quand on n'est pas dans le cas de l'exception neutrality.

                  C'est une "simple" problématique de maintenance du code : si on est exception neutral, on a la garantie que le code reste valide et on peut uiliser sans problème. Si on n'a pas cette garantie, on doit regarder au cas par cas (donc plus de maintenance).

                  Pour revenir a l'exemple de "Effective Modern C++", Meyers explique justement que cette garantie saute si on a noexcept.

                  • Partager sur Facebook
                  • Partager sur Twitter
                    5 février 2019 à 11:57:18

                    Ben, pour moi, le terme neutralité implique que, quoi qu'il advienne, tout ce qui peut se passer en aval (au niveau des fonctions appelées, éventuellement de manière indirecte) de la fonction sera répercuté "tel quel" en amont (au niveau des fonctions appelantes).

                    Il est vrai que, avant C++11, on ne pouvait fournir cette garantie que s'il n'y avait pas changement de type au niveau de l'exception au passage, à cause de la possibilité que l'on avait de limiter le  nombre de types d'exception qu'une fonction acceptait de transmettre.

                    Mais le fait est que ce problème ne se pose maintenant plus: la transmission d'exception se fait en "tout ou rien": si une fonction n'est pas noexcept, elle accepte automatiquement de transmettre n'importe quel type d'exception, et donc, la crainte de voir une fonction amont refuser de transmettre une exception parce que l'on en a changé le type devient donc sans objet.

                    La restriction imposée par cette crainte n'a donc plus la moindre raison d'être.

                    Pour moi, toute fonction non noexcept qui ne "bloque" pas une exception au niveau d'un catch (en relançant une autre exception, quelle quelle soit) s'avère tout aussi neutre au niveau des exceptions que n'importe quelle fonction qui n'essayerait même pas d'attraper les exceptions, pour la raisons toute bête que, maintenant, les fonctions amont n'ont plus l'occasion de refuser de transmettre une exception sous prétexte qu'elle n'est pas du type approprié.

                    Ou alors, c'est que le terme exception neutrality a été mal choisi dans le sens où il n'exprime pas correctement de lui-même ce qu'il est sensé représenter

                    -
                    Edité par koala01 5 février 2019 à 11:59:02

                    • Partager sur Facebook
                    • Partager sur Twitter
                    Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait

                    étude de noexcept sur exemples

                    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                    • Editeur
                    • Markdown