Partage
  • Partager sur Facebook
  • Partager sur Twitter

Copier un std::unique_ptr

    1 septembre 2022 à 17:37:14

    Bonjour je me suis mis récemment au C++, avant je faisais du C. J'essaie de comprendre le fonctionnement des std::unique_ptr.

    Je vous explique le problème, je dois passer un pointeur à une fonction, mais peut être que la fonction va renvoyer false, auquel cas je veux "annuler" le std::move, car je veux utiliser la valeur que je lui ai passé.

    void foo() {
      std::unique_ptr<Bar> bar = std::make_unique<Bar>();
      
      bool succes = maFonction(std::move(bar));
    
      if (!succes) {
        // je veux que bar soit valide ici (je veux "annuler" le move)
        bar->...
      }
    }
    
    bool maFonction(std::unique_ptr<Bar> bar) {
      if (...) {
        autre_bar = std::move(bar); // on s'approprie le bar car c'est ce qu'on veut faire en cas de succes
        return true;
      }
    
      return false;
    }

    Avec des pointeurs à la C, c'est très simple, on passe le pointeur et on peut toujours l'utiliser par la suite si besoin, car un pointeur à la C n'est pas censé se faire free par le premier venu (sinon il aurait fallu utiliser un pointeur intelligent n'est-ce pas ?)

    Du coup comment je fais si je veux utiliser bar après le move ? Est ce qu'il faut que je fasse une copie complète de l'objet ? 

    • Partager sur Facebook
    • Partager sur Twitter
      1 septembre 2022 à 17:53:12

      unique_ptr, c'est une patate chaude. Il ne peut y avoir qu'un seul responsable à la fois. Et quand on le move, en fait, ce que l'on move vraiment, c'est la responsabilité sur ce pointeur.

      Ensuite une fonction qui prend un unique_ptr est une fonction puit qui s'empare obligatoirement de la responsabilité.

      Et une fonction qui renvoie un unique_ptr est une fonction source qui refile obligatoirement la responsabilité à la variable qui réceptionne.

      ---

      Ce mode de fonctionnement peut t'enquiquiner par rapport à ta volonté de ne pas refiler, mais franchement, tu ne veux pas d'une fonction qui suivant la phase de la lune va s'emparer ou non de la responsabilité sur un pointeur. Mieux vaut revoir le désign.

      Sinon il faut prendre par référence et souffrir de ne pouvoir jamais etre sur si on peut continuer ou non à utiliser le pointeur

      -
      Edité par lmghs 1 septembre 2022 à 17:55:20

      • Partager sur Facebook
      • Partager sur Twitter
      C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
        1 septembre 2022 à 17:58:35

        Tu ne peux pas annuler un move.

        En C, ce n'est pas plus simple. C'est une complexité différente. Comme il n'est pas possible d'exprimer quel type de passage de paramètre on veut faire dans une fonction (faire une copier, transférer la propriété, passer une indirection constante ou non, etc.), cela reporte la responsabilité du nettoyage des ressources sur le développeur. Qui peut faire des erreurs. Avec du code plus expressif, on peut avoir des garanties plus fortes (= le compilateur peut vérifier que ce que l'on veut exprimer est valide).

        Dans ton cas, ta fonction "maFonction" ne veut pas faire un transfère de propriété (move), mais modifier conditionnellement le pointeur. Donc c'est juste une référence non constante qui répond à ce besoin. Un truc comme ca :

        void foo() {
          std::unique_ptr<Bar> bar = std::make_unique<Bar>();
           
          bool succes = maFonction(bar);
         
          if (!succes) {
            // je veux que bar soit valide ici (je veux "annuler" le move)
            bar->...
          }
        }
         
        bool maFonction(std::unique_ptr<Bar>& bar) {
          if (...) {
            autre_bar = std::move(bar); // on s'approprie le bar car c'est ce qu'on veut faire en cas de succes
            return true;
          }
         
          return false;
        }

        Par contre, je suis pas fan de ce bool en retour de fonction. C'est aussi un risque d'erreur (puisque cela exprime au final la meme chose que "bar == nullptr"). Je préfère probablement un truc comme :

        void foo() {
          std::unique_ptr<Bar> bar = std::make_unique<Bar>();
           
          maFonction(bar);
         
          if (bar) {
            // je veux que bar soit valide ici (je veux "annuler" le move)
            bar->...
          }
        }
         
        void maFonction(std::unique_ptr<Bar>& bar) {
          if (...) {
            autre_bar = std::move(bar); // on s'approprie le bar car c'est ce qu'on veut faire en cas de succes
          }
        }

        Mais je suis pas fan non plus de ce passage par référence. Pour aller plus loin, peut être que "maFonction" peut être considéré comme faisant partie de l'initialisation de bar et donc avoir une sorte de factory qui ressemble a ca :

        void foo() {
          std::unique_ptr<Bar> bar = maFonctionCreate();
            
          if (bar) {
            // je veux que bar soit valide ici (je veux "annuler" le move)
            bar->...
          }
        }
         
        std::unique_ptr<Bar> maFonctionCreate() {
          std::unique_ptr<Bar> bar = std::make_unique<Bar>();
        
          if (...) {
            autre_bar = std::move(bar); // on s'approprie le bar car c'est ce qu'on veut faire en cas de succes
          }
         
          return bar;
        }

        Par exemple. Mais il y a peu être d'autres designs a considérer, en fonction de ce que tu veux faire concrètement. Par exemple que le "if (...)" soit directement dans "foo()", pour initialiser directement le bon pointeur et éviter un move et un if inutile.

        void foo() {
          if (...) {
            autre_bar = std::make_unique<Bar>();
          }
          else {
            auto bar = std::make_unique<Bar>();
            bar->...
          }
        }

        (C'est le problème des codes d'exemple abstraits, on sait pas a quel point on peut simplifier).

        -
        Edité par gbdivers 1 septembre 2022 à 18:09:02

        • Partager sur Facebook
        • Partager sur Twitter
          1 septembre 2022 à 17:59:22

          Je vais sortir mon analogie avec la balle de tennis, elle ne peut pas être des deux côtés du filet en même temps.

          La responsabilité incombe à celui qui prend le pointeur en charge. C'est également vrai en C.

          -
          Edité par PierrotLeFou 1 septembre 2022 à 18:01:11

          • Partager sur Facebook
          • Partager sur Twitter

          Le Tout est souvent plus grand que la somme de ses parties.

            1 septembre 2022 à 18:09:11

            Ton approche n'est pas "exception compatible".

            S'il y a une exception, qui est responsable du nettoyage ?

            Le retour de type "bool" est, pour moi, bien trop C-esque que C++.

            Pourquoi ne pas retourner une "<optional>".

            Si "maFonction" ne renvoi rien, on fait rien, sinon, on travaille sur le machin retourné, qui sera peut-être le truc passé en paramètre (ou pas).

            • Partager sur Facebook
            • Partager sur Twitter
            Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
              2 septembre 2022 à 22:15:50

              gbdivers a écrit:

              Dans ton cas, ta fonction "maFonction" ne veut pas faire un transfère de propriété (move), mais modifier conditionnellement le pointeur. 


              Justement je pense que "mafonction" a besoin de faire un transfère de propriété car elle va ranger dans un coin l'objet qu'on lui passe en paramètre, sauf en cas d'erreur, auquel cas elle ne s'approprie pas l'objet.

              bacelar a écrit:

              Ton approche n'est pas "exception compatible".

              S'il y a une exception, qui est responsable du nettoyage ?

               Je n'ai pas compris, quel est le problème ? Si il y a  une exception  ça va bien appeler les destructeurs des objets en cours et ça marche non ?



              • Partager sur Facebook
              • Partager sur Twitter
                2 septembre 2022 à 22:53:28

                LucieNottin1 a écrit:

                a besoin de faire un transfère (...) sauf (...) elle ne s'approprie pas l'objet

                Donc c'est pas tout le temps un transfère.

                Il y a bien un transfère dans le code que je t'ai donné, mais pour `autre_bar`, pas pour l'appel a la fonction `maFonction`.

                • Partager sur Facebook
                • Partager sur Twitter
                  3 septembre 2022 à 1:01:56

                  >Si il y a  une exception  ça va bien appeler les destructeurs des objets en cours et ça marche non ?

                  Ok, mais si tu catches dans la fonction "foo", on peut utiliser "bar" dans la catch ? dans le finally ? à la suite dans la fonction "foo" ?

                  Pouvez-vous donner un cas concret de votre design et montrer en quoi il est plus avantageux qu'une approche plus "classique" ?

                  Pourquoi ne pas renvoyer l'objet dans les valeurs de retours, en cas de "non appropriation/erreur"?

                  L'objet a été créé dans foo, s'il y a une exception, c'est au niveau de cette fonction que doit être réglé "le problème", car les fonctions appelantes n'ont pas cette information.

                  • Partager sur Facebook
                  • Partager sur Twitter
                  Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                    3 septembre 2022 à 2:50:25

                    Salut,

                    La question que tu dois te poser, lorsque tu  as un pointeur unique, c'est "la fonction à laquelle je veux transmettre la ressource sous-jacente doit elle prendre la responsabilité de libérer cette ressources?"

                    Et la réponse doit être ferme: c'est oui, ou c'est non. Il n'y a pas de "peut-être" ou de "en fonction de" qui tienne. S'il y a un "peut être" ou un "en fonction de" qui intervient dans ta réponse, c'est que c'est "non".  C'est aussi simple que cela ;)

                    Pour faire simple, une fois que "quelque chose" a pris la responsabilité de maintenir une ressource en mémoire grâce à un pointeur unique -- comprends: une fois que la ressource a été créé et placée dans un pointeur unique -- il y a "de très fortes probabilités" pour que cette chose garde la responsabilité de la ressource "jusqu'au bout".

                    Autrement dit, il n'y a que si tu prévois de détruire "la chose" qui s'occupe de maintenir ta ressource en mémoire que tu va -- effectivement -- envisager de transmettre cette responsabilité à "quelque chose d'autre"; et uniquement si il y a de ** bonnes raisons ** pour que la ressource "survive" à ce qui avait pris la responsabilité d'assurer son maintient en mémoire à la base.

                    On peut donc très raisonnablement dire que la réponse à cette première question sera négative dans la très grosse majorité des cas.

                    Et, à partir de cette conclusion, on peut se dire que, dans 90% des cas dans lesquels la fonction appelée (ou l'instance de classe à partir de laquelle la fonction a été appelée) n'a aucune raison de prendre la responsabilité de libérer la ressource sous-jacente, tu auras sans doute largement intérêt à transmettre cette ressource sous la forme d'une référence (éventuellement constante) plutôt que sous la forme d'un pointeur.

                    La raison est simple :  la fonction appelée voudra sans doute partir "du principe" que la ressource existe bel et bien et qu'elle est dans un état valide.

                    S'il faudra -- effectivement -- s'assurer que la ressource est bel et bien dans un état valide avant de la manipuler, l'utilisation d'une référence rend strictement impossible le fait que la ressource puisse ne pas exister lorsque l'exécution de la fonction commence ce qui rend -- hormis dans un contexte multi-thread "mal agencé" -- quasiment impossible le fait que la ressource ne "cesse d'exister" avant que la fonction n'arrive à son terme.

                    Les dix pourcents restants correspondront:

                    • au fait que puisse vouloir transmettre la ressource à une fonction "C" (tu seras alors obligé de la transmettre sous forme de pointeur)
                    • au fait qu'il puisse effectivement "de temps en temps" une fonction ou une autre pour laquelle tu puisse considérer comme "correct et normal" le fait que la ressource soit purement et simplement inexistante.

                    Cependant, il faut bien se dire que ces deux cas particuliers sont en théorie extrêmement rares. Pour être complet, il est même "fort vraisemblable" que l'utilisation de std::optional puisse avoir un certain attrait dans le deuxième cas ;)

                    • 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
                      12 septembre 2022 à 12:32:40

                      Bonjour, merci pour vos réponses, en fait je sais pas comment faire, j'ai besoin d'avoir une structure de graphe, et dans les noeuds je ne sais pas si je doit mettre des unique_ptr ou des shared_ptr. Au début je pensais que j'allais mettre un unique_ptr pour dire que chaque noeud est propriétaire de ses fils mais je me rend compte qu'en fait il peut y avoir des noeuds qui pointent vers d'autre noeuds existants, du coup je me suis dit qu'il fallait mettre des shared_ptr mais je sais pas si c'est bien ce qu'il faut faire. Si je le faisais en C j'aurai mis des pointeurs normaux mais là je vois pas comment faire
                      • Partager sur Facebook
                      • Partager sur Twitter
                        12 septembre 2022 à 14:40:29

                        S'il y a potentiellement plusieurs propriétaire simultané de l'objet pointé : shared_ptr.

                        Si les performances ne suivent pas, il faut changer cet état de fait (plusieurs ....).

                        • Partager sur Facebook
                        • Partager sur Twitter
                        Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                          12 septembre 2022 à 22:18:03

                          En fait je sais pas qui sont les propriétaires, l'idée est que dans le graphe il y a des noeuds qui pointent vers d'autres noeuds, d'ailleurs ces liens peuvent changer en cours de route, et il peut aussi y avoir un noeud A qui pointent vers B et B qui pointent lui même vers A. Du coup je pense qu'il faut pas mettre de unique_ptr car c'est pas parce qu'un lien disparait qu'il faut supprimer le noeud à l'autre bout, peut être que d'autres noeuds pointent vers lui. Et qu'est ce que vous entendez par "si les performance ne suivent pas" ? C'est à cause des shared_ptr ? Pour tout vous dire j'en ai à peu près rien à faire des performances, je veux juste un code correct et bien organisé
                          • Partager sur Facebook
                          • Partager sur Twitter
                            12 septembre 2022 à 23:33:53

                            Si tu as des cycles, les noeuds ne peuvent plus être propriétaires d'autres noeuds. Et si en plus tu réorganises les liens...

                            Je dirais que le plus simple est probablement de distinguer la possession des noeuds/leur vie, de leur organisation. D'un côté un vecteur de unique_ptr, voire un vecteur de noeuds directement. Et de l'autre, les noeuds disposent de simple pointeurs vers d'autres noeuds. Certains utiliseront des surcouches claires (observer_ptr, non_owning_ptr) pour lever tout doute.

                            • Partager sur Facebook
                            • Partager sur Twitter
                            C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                              13 septembre 2022 à 1:26:31

                              L'utilisation d'un unique_ptr n'a pas de surcoût par rapport à un pointeur nus "correctement" géré, mais un shared_ptr en a un (fonction de l'implémentation).

                              Un shared_ptr est bien plus flexible/polyvalent qu'un unique_ptr, mais aussi, par la force des choses plus "ambiguë".

                              Avec des shared_ptr, vous pouvez très facilement changer en profondeur vos algorithmes sans trop de casse alors qu'avec des unique_ptr, il faut être plus malin pour que cela soit le cas. L'exemple de @lmghs est une méthode plus "maligne" que de bêtement aligner les unique_ptr et galérer au moindre changement de design.

                              Pour moi, si vous n'avez pas de contrainte de design, ni d'apprentissage de "comment utiliser intelligemment les unique_ptr", vous emmerdez pas, utilisez des shared_ptr.

                              • Partager sur Facebook
                              • Partager sur Twitter
                              Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                                13 septembre 2022 à 2:50:55

                                Malheureusement,

                                - "d'ailleurs ces liens peuvent changer en cours de route,"
                                - plus "et il peut aussi y avoir un noeud A qui pointent vers B et B qui pointent lui même vers A."

                                font que des shared_ptr + weak_ptr ne résoudront rien. A moins qu'il y ait malgré tout une sorte de séniorité entre les noeuds: avec des noeuds qui pointent plus "fort" que d'autres, ces deux contraintes ne peuvent pas se modéliser avec des shared_ptr+weak. Certains GC savent gérer de tels cycles couplés au besoin de dynamisme, mais pas pointeurs intelligents à comptage de référence.

                                Il faut vraiment découpler la gestion de la durée de vie de la gestion de la topologie du graphe.

                                • Partager sur Facebook
                                • Partager sur Twitter
                                C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                                  17 septembre 2022 à 13:13:58

                                  Oui les shared_ptr sont limités. Si les sémantiques de propriété (qui possède quoi etc) ne sont pas clairement définis alors les smart pointers ne sont pas d'une grande aide. Imaginez, vous commencez en vous disant que chaque noeud possède ses fils (ce qui parait raisonnable de prime abord), donc vous mettez des unique_ptr, et là flute ! Vous vous rendez compte que plusieurs personnes ont envie de pointer vers le même noeud, et donc vous êtes obligé de faire une copie, ça serait balot ! Non seulement c'est moins performant de copier mais en plus ça fait que si vous mettez à jour le premier, les copies ne sont plus à jour, on ne peut rien partager... Aussi, j'espère que votre arbre n'est pas trop grand car le destructeur du root va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils,  c'est le meilleur moyen d'exploser la stack

                                  Pour éviter ce genre de désagrément, il faut utiliser des pointeurs nus, qui sont très flexibles, on peut faire une copie en profondeur si besoin, on peut partager des noeuds, avoir des cycles, etc.

                                  Evidemment on a pas envie de faire des new à la main car on ne sait pas qui va faire le delete et quand, donc il faut donner cette responsabilité à quelqu'un d'autre. Un de vos grands amis a fait une présentation à ce sujet https://www.youtube.com/watch?v=JfmTagWcqoE&t=3558s en gros son idée c'est comme une arena mais en moins performant et plus "safe" https://github.com/hsutter/gcpp 

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    17 septembre 2022 à 16:27:58

                                    JadeSalina a écrit:

                                    Aussi, j'espère que votre arbre n'est pas trop grand car le destructeur du root va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils qui va appeler le destructeur du fils, c'est le meilleur moyen d'exploser la stack

                                    Non, même pas... Car, lorsque les choses sont "aussi simples" que cela, et que la destruction d'un noeud occasionne la destruction de tous les noeuds "enfants", l'appel à un des destructeur tient ... dans la taille d'un entier, ou peu s'en faut.

                                    Si bien que,avant de risquer d'exploser la stack à coup d'appel récursifs au destructeur, il faudrait que ton arbre soit à peu près aussi large que haut et qu'il contiennent un nombre d'éléments tel qu'il y ait peu de chance que tu aie eu l'occasion de tous les faire entrer en mémoire

                                    JadeSalina a écrit:

                                    Pour éviter ce genre de désagrément, il faut utiliser des pointeurs nus, qui sont très flexibles, on peut faire une copie en profondeur si besoin, on peut partager des noeuds, avoir des cycles, etc.

                                    Non, justement...  Pour éviter ce genre de désagrément, il faut, "tout simplement" de définir plus précisément "qui peut faire quoi" des ressources utilisées et, de préférence, s'assurer que celui qui "peut utiliser" une ressource sera différent de celui qui "doit maintenir" la ressource en mémoire et qui peut, lui, la détruire effectivement (le cas échéant, quand certaines conditions sont respectées).

                                    Les pointeurs nus sont une véritable catastrophe dans le cas présent, parce que:

                                    • Si tu détruit une ressource qui est encore référencée (par un pointeur) "quelque part", il arrivera ** forcément ** un moment où tu essayeras d'accéder (ou pire de modifier) cette ressource qui n'existe plus, et ton programme plantera
                                    • Si tu perds toutes référence à une ressource sans l'avoir détruite au préalable, tu subira une fuite mémoire, "insignifiante" à elle seule sur une courte durée, catastrophique par sa multiplication sur une durée plus importante, car, à terme, c'est l'ensemble du système qui finira par planter par manque de ressources.

                                    Il est possible de s'en sortir avec des pointeurs nus, je ne dirai pas le contraire.  Mais crois moi: cela va te demander tellement de temps (que tu aurais pu passer à faire autre chose "de plus intéressant"), d'essais, d'erreurs et de modifications que tu n'a pas envie de te lancer dans ce genre d'aventure, à moins que ce ne soit à but pédagogique.

                                    Les choses sont d'autant plus  complexes lorsque tu es face à un graphe, parce que:

                                    • un noeud peut être référencé par plusieurs autres noeuds: il n'est pas impossible que plusieurs noeuds différents nous amènent (plus ou moins) directement au même noeud dans le graphe
                                    • un noeud non réfrencé n'est pas forcément un noeud qui peut être détruit: il peut "tout simplement" n'être pas utilisé dans le graphe en cours d'élaboration, mais rester utile / nécessaire pour la suite: ce n'est pas parce que aucune route ne mènerait à l'Atomnium, à la tour Eiffel ou à la statue de la Liberté que toute référence à celles-ci doivent être supprimées.  Il en va de même pour les noeuds "moins prestigieux" ;)
                                    • Il faudra de toutes manières répondre à la question "que faire si je décide de détruire un noeud?"
                                      • Dois-je détruire tous les noeuds auxquels le noeud donne accès, ainsi que les noeuds auxquels les noeuds que j'aurai détruit en plus donnent accès et ainsi de suite?
                                      • Dois-je faire en sorte de donner l'accès aux noeuds auxquels le noeud détruit donne accès "à d'autre noeuds"? si oui comment déterminer à quels noeuds donner accès à ces noeuds?"
                                      • Y a-t-il une autre possibilité à laquelle je n'aurais pas songé?
                                    • Et sans doute bien d'autres problèmes auxquels je n'ai pas la tête à réfléchir pour l'instant

                                    Tout ce que je peux dire avec certitude, c'est que l'utilisation de pointeurs nus ne fera qu'empirer une situation déjà largement assez complexe. 

                                    JadeSalina a écrit:

                                    Imaginez, vous commencez en vous disant que chaque noeud possède ses fils (ce qui parait raisonnable de prime abord), donc vous mettez des unique_ptr, et là flute ! Vous vous rendez compte que plusieurs personnes ont envie de pointer vers le même noeud,

                                    C'est justement là tout le problème: il ne faut pas partir du principe qu'un noeud possède ses fils lorsque l'on parle de graphe.

                                    Il faut partir du principe qu'un noeud référence les noeuds auxquels il est relié, en laissant la possession à "quelque chose" d'autre.

                                    C'est un peu le même principe que celui que l'on retrouve en gestion de base de données: Si l'on en arrive à un point où il existe une relation N ... N entre les éléments A et les éléments B, il faut s'arranger pour "subdiviser"  cette relation en créant deux éléments  intermédiaires qui permettront:

                                    • d'avoir une relation N ...1 entre les éléments A et cet élément intermédiaire
                                    • d'avoir une relation 1 ... N entre le deuxième intermédiaire et les éléments B
                                    • d'avoir une relation 1 ... 1 entre le premier intermédiaire et le deuxième.

                                    Dans le cas présent, nous aurions donc:

                                    • Chaque noeud référence "un certain nombre" de noeuds auxquels il donne accès
                                    • Pour chaque noeud, nous disposons de la liste des noeuds qui les référencent
                                    • Si un noeud n'est référencé nulle part, c'est un "candidat idéal" à la destruction (il faut voir quand et comment il sera décidé de le détruire)

                                    -
                                    Edité par koala01 17 septembre 2022 à 16:28:55

                                    • 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
                                      17 septembre 2022 à 17:23:37

                                      koala01 a écrit:

                                      Les pointeurs nus sont une véritable catastrophe dans le cas présent, parce que:

                                      • Si tu détruit une ressource qui est encore référencée (par un pointeur) "quelque part", il arrivera ** forcément ** un moment où tu essayeras d'accéder (ou pire de modifier) cette ressource qui n'existe plus, et ton programme plantera

                                      ...

                                      Il faut partir du principe qu'un noeud référence les noeuds auxquels il est relié, en laissant la possession à "quelque chose" d'autre.

                                      Justement dans le cas présent, comment vous faites pour référencer un noeud en laissant la possession à autre chose sans pointeur nu ? À la limite on peut imaginer un observer_ptr qui se mettrait à nullptr quand le noeud pointé se fait supprimer, mais dans tous les cas votre premier point on ne peut rien faire pour l'éviter.

                                      koala01 a écrit:

                                      Tout ce que je peux dire avec certitude, c'est que l'utilisation de pointeurs nus ne fera qu'empirer une situation déjà largement assez complexe. 

                                      Justement avec les pointeurs nus ça simplifie le problème, n'importe qui peut pointer vers n'importe qui, d'ailleurs Herb Sutter est en train de faire une surcouche de C++ avec du sucre vanillé et du coulis à la fraise par dessus, et devinez quoi, il stocke les noeuds avec des pointeurs nus https://github.com/hsutter/cppfront/blob/8140ab920a38e9cc66d1862c9b823a98a9f25e3b/source/sema.h#L34 

                                      Ce qui veut dire que si son arbre est supprimé mais pas les "symboles", ils vont pointer vers n'importe quoi. Et pourquoi on s'en fiche complètement ? Car jamais des noeuds ne seront détruits comme ça en plein milieu, donc on est sûr que les pointeurs nus seront valides. C'est beaucoup plus malin de faire comme ça plutôt que de se casser la tête à essayer de se demander quand supprimer un noeud et faire en sorte que plus personne pointe dessus etc. Il ne faut pas se rajouter des problèmes qui n'existent pas 

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        18 septembre 2022 à 9:52:37

                                        JadeSalina a écrit:

                                        Justement avec les pointeurs nus ça simplifie le problème, n'importe qui peut pointer vers n'importe qui, d'ailleurs Herb Sutter est en train de faire une surcouche de C++ avec du sucre vanillé et du coulis à la fraise par dessus, et devinez quoi, il stocke les noeuds avec des pointeurs nus https://github.com/hsutter/cppfront/blob/8140ab920a38e9cc66d1862c9b823a98a9f25e3b/source/sema.h#L34 

                                        Ce qui veut dire que si son arbre est supprimé mais pas les "symboles", ils vont pointer vers n'importe quoi. Et pourquoi on s'en fiche complètement ? Car jamais des noeuds ne seront détruits comme ça en plein milieu, donc on est sûr que les pointeurs nus seront valides. C'est beaucoup plus malin de faire comme ça plutôt que de se casser la tête à essayer de se demander quand supprimer un noeud et faire en sorte que plus personne pointe dessus etc. Il ne faut pas se rajouter des problèmes qui n'existent pas 

                                        Normal, faire des arbres avec des smart pointers est plutôt inadapté. On pourrait éventuellement argument sur l'utilisation des std::weak_ptr mais c'est un des rares cas (avec les listes chainées) où les pointeurs bruts peuvent servir.

                                        On a jamais dit que les smart pointeurs étaient la réponse à tout, on a juste dit que les pointeurs bruts sont à utiliser avec parcimonie.

                                        • Partager sur Facebook
                                        • Partager sur Twitter

                                        git is great because Linus did it, mercurial is better because he didn't.

                                          18 septembre 2022 à 10:24:41

                                          Les pointeurs bruts ne sont pas réellement une solution au problème ici. Mais il faut d'abord bien comprendre le problème.

                                          Les pointeurs intelligents imposent de définir clairement l'ownership, c'est a dire qui est responsable d'un noeud dans ce cas (un seul owner pour unique_ptr, le dernier owner dans le cas de shared_ptr).

                                          Dans le cas d'un graphe complexe (pas hiérarchisé), le problème est qu'il est difficile de définir correctement et clairement l'ownership. Et donc unique_ptr et shared_ptr posent généralement des problèmes.

                                          Une solution est de simplifier la complexité. Par exemple en créant un vector de unique_ptr qui aura l'ownership et que les noeuds n'ont pas du tout l'ownership (solution proposée par lmghs).

                                          (Jusqu'ici, je ne fais que résumer ce que lmghs et koala01 ont déjà expliqué)

                                          Les pointeurs nus ne permettent pas de régler ce problème de définition de qui a l'ownership. Ils n'ont juste pas cette contrainte d'avoir un ownership précis et donc ils peuvent être utilisé quand l'ownership n'est pas défini. Mais ils ne font que décaler le problème de définition de l'ownership.

                                          C'est pour cela qu'on conseille d'utiliser les pointeurs intelligents : parce qu'ils force à avoir un ownership bien défini. Et donc de permettre de garantir (par le compilateur) que la libération des ressources serait correctement faite par l'owner. Les pointeurs nus sont souvent qu'un moyen pour les mauvais devs de faire du code moisi, sans owneship strict (et souvent avec des bugs).

                                          -
                                          Edité par gbdivers 18 septembre 2022 à 10:32:25

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            18 septembre 2022 à 11:29:25

                                            gbdivers a écrit:

                                            C'est pour cela qu'on conseille d'utiliser les pointeurs intelligents : parce qu'ils force à avoir un ownership bien défini. Et donc de permettre de garantir (par le compilateur) que la libération des ressources serait correctement faite par l'owner. Les pointeurs nus sont souvent qu'un moyen pour les mauvais devs de faire du code moisi, sans owneship strict (et souvent avec des bugs).

                                            Oui les pointeurs nus qui sont en fait propriétaires en douce c'est pas bien, mais les pointeurs nus qui  ne font que pointer vers le vrai propriétaire comme par exemple avec le vecteur de unique_ptr ça pose pas de problème et on est sûr que ça sera bien libéré. Par contre c'est pas optimal le vecteur de unique_ptr il vaut mieux faire un memory pool

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              18 septembre 2022 à 12:18:46

                                              Tu aurais du regarder la video de Herb Sutter dans son entièreté, car, encore une fois, tu n'a pris qu'une partie du message qui semblait correspondre à ce que tu crois, au risque de la déformer pour que cela soit le cas.

                                              Ce que dit le monsieur, et que nous répétons également à l'envie depuis bien avant cette conférence, c'est:

                                              • Utilisez de préférence la pile plutôt que le tas: ayez autant que possible recours à des données locale ou à des données membres, sans allocation dynamique de la mémoire
                                              • Si vous n'avez pas le choix et que vous devez avoir recours à l'allocation dynamique de la mémoire, utilisez de préférence les pointeurs uniques (std::unique_ptr), car il présente l'énorme avantage de limiter la décision de libérer la ressource à un seul endroit
                                              • Si les pointeurs uniques ne sont vraiment pas une solution, utilisez de préférence les shared_ptr et les les weak_ptr, car il permettront de libérer la ressource lorsque le dernier possesseur de celle-ci n'en aura plus besoin
                                              • Si vous n'avez vraiment pas d'autre choix, alors une "autre solution" mettant en oeuvre une libération des ressources de manière indirecte devra être envisagée

                                              Note au passage qu'il évalue également les probabilités de se retrouver dans les  différentes situations:  la première est évaluée à 80% des cas et les deux suivantes (celles qui concernent les pointeurs intelligents en générale) à 20% des cas, alors que la toute dernière est évaluée à 1% des cas.

                                              As tu remarqué que cela fait un total de ... 101%?  Non, ce n'est pas une erreur, si ce n'est peut être une erreur d'arrondi.  Nous avons peut être arrondi la première possibilité à 80% parce que la probabilité réel%%le était de l'ordre de 79.796% et la deuxième à 20% parce que la probabilité réelle était de l'ordre de 19.684%, ce qui aurait donné une probabiltié de 0.51% pour la dernière possibilité.  Les règles d'arrondis ont été respectées, et tout le monde peut être content.

                                              Il est vrai que les pointeurs intelligents dont on dispose actuellement ne sont pas ** forcément ** adaptés à toutes des situations auxquelles nous pourrions être confrontés.

                                              Et c'est normal: chaque projet est soumis à des contraintes et à des besoins qui lui sont propres.  Quand les besoins changent, les solutions à envisager changent également.  C'est la règle immuable du développement.

                                              Pour la cause, les pointeurs intelligents dont disposent n'en sont pas "bons à jeter" pour  autant.  Au contraire: il nous donnent la marche à suivre pour -- en cas de  besoin -- nous permettre d'ajouter la notion qui pourrait nous manquer.

                                              Et cette marche à suivre est finalement toute simple: il faut utiliser les comportements de base d'une donnée classique (entre autre, le fait que le destructeur sera automatiquement appelé) pour s'assurer que la ressource sous-jacente sera correctement libérée lorsque la donnée qui la contient sera elle-même détruite.

                                              C'est "tout ce que fait et dit" Sutter dans cette conférence, adapté à une situation à laquelle les gens sont régulièrement confrontés: la gestion de graphes:

                                              Il part du constat que les pointeurs intelligents dont on dispose actuellement ne sont effectivement pas adaptés à la situation.  Toute personne sensée ayant manipulé des graphes depuis bien avant C++11 aurait pu le dire: la libération des ressources utilisées par les noeuds ne peut pas se faire "en direct", à la manière dont le font std::unique_ptr et std::shared_ptr.

                                              Il propose donc une solution au travers de laquelle la libération effective des ressources se fera de manière "réfléchie", une fois acquise la certitude qu'elles ne sont effectivement plus nécessaires.

                                              Et il va plus loin en montrant que cette solution s'adapte aussi bien aux différentes combinaisons de pointeurs intelligents que le reste.

                                              Quoi qu'il en soit, il ne plaide absolument pas pour l'utilisation de pointeurs bruts.  Au contraire.  Il plaide à la limite pour l'ajout d'une notion permettant de résoudre un problème particulier, et il apporte une "proof of concept" fonctionnelle de cette solution.

                                              JadeSalina a écrit:

                                              Justement dans le cas présent, comment vous faites pour référencer un noeud en laissant la possession à autre chose sans pointeur nu ? À la limite on peut imaginer un observer_ptr qui se mettrait à nullptr quand le noeud pointé se fait supprimer, mais dans tous les cas votre premier point on ne peut rien faire pour l'éviter.

                                              Avec cette fameuse notion que Herb Sutter a appelée deferred_ptr, ou que lmghs a appelée observer_ptr ou non_owning_ptr dans son intervention précédente.

                                              En fait, peut importe le terme qui sera utilisé pour représenter cette notion. Ce qui importe, c'est de disposer d'une notion permettant de donner un "GO / NO GO" à la libération d'une ressource dont elle n'est pas propriétaire et sur laquelle elle n'a qu'un droit de manipulation et qui pourra être "mise à jour" quant à la disponibilité de la ressource au besoin.

                                              Les règles concernant la durée de vie des données feront le reste, étant donnée que les données sont toujours détruites dans l'ordre inverse de leur déclaration / création.

                                              -
                                              Edité par koala01 18 septembre 2022 à 12:37:17

                                              • 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

                                              Copier un std::unique_ptr

                                              × 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