Partage
  • Partager sur Facebook
  • Partager sur Twitter

Eviter les fuites de mémoire, question méthode

Sujet résolu
    14 septembre 2020 à 16:13:35

    Bonjour,

    Il y a une question qui me trotte dans la tête portant sur l'allocation dynamique dans un cas précis. je n'ai encore jamais eu la réponse, certainement parce que je n'ai jamais pu l'exprimer correctement. Pour me faire comprendre, je vais passer par un exemple. Supposons qu'on a la classe Exemple suivante:

    class Exemple
    {
    public:
    Exemple(t_Coordonnee *coord);
    
    private:
    int x, y;
    };

    t_Coordonnee est une structure qui possède deux int. Dans le main, on déclare une instance de Exemple de la manière suivante:

    Exemple exemple(new t_Coordonnee());

    On a donc fait une allocation dynamique de la structure t_Coordonnee dans le constructeur d'exemple. Mais dans ce cas précis, comment assurer la libération de la mémoire? Le t_Coordonnée est directement utilisé dans le constructeur et n'est pas un attribut de la classe. J'ai observé cette pratique en récupérant le code de quelqu'un et je me demande si c'est une bonne pratique ou si c'est fuite de mémoire assurée.

    Merci de vos lumières



    T_coordonnee est une structure quelconque. Dans le main, on déclare un 

    • Partager sur Facebook
    • Partager sur Twitter
      14 septembre 2020 à 16:33:52

      Avec l'exemple d'appel que tu as montré, c'est fuite assurée. Cela ressemble beaucoup à du Java porté syntaxiquement en C++ sans l'adapter comme il aurait d^u l'^etre surtout pour des points.

      Les diverses raisons que l'on pourrait avoir à recevoir un pointeur ici

      - pour éviter de copier un gros truc sur la pile. A ce compte là, il faut employer des références

      - parce que la classe Exemple devient responsable de la mémoire passée en paramètre --> En 2011, le C++(11) a introduit les `std::unique_ptr`  il sont faits pour cela: forcer l'appelé à devenir responsable. En plus ils garantiront la libération de mémoire.

      - parce qu'il y a un c^oté optionnel --> boost::optional/std::optional apporteront une sémantique plus claire

      Mais si ton truc a 2 juste coordonnées... alors clairement, il faut virer tous ces pointeurs.

      • 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.
        14 septembre 2020 à 16:55:30

        C'est un exemple. En réalité il y a beaucoup de propriété dans les différentes structures utilisées.

        J'avais en effet entendu parler de ces pointeurs intelligents, mais je ne m'y étais encore jamais attarder. Merci de ton aidlmghs. Je vais en apprendre plus sur les std::unique_ptr et modifier tout ce code.

        • Partager sur Facebook
        • Partager sur Twitter
          14 septembre 2020 à 17:22:54

          Comme l'a dit lmghs, si tu ne conserves pas l'objet dans ta classe, c'est probablement une reference qu'il faut utiliser.
          • Partager sur Facebook
          • Partager sur Twitter
            16 septembre 2020 à 9:09:35

            L'expression suivante...

            new t_Coordonnee()

            ...te retourne une adresse (un entier 32 ou 64 bits), l'adresse du début (l'adresse du premier octet) du bout de mémoire qu'il vient d'allouer pour contenir la valeur de ton nouvel objet t_Coordonnee. Après ça, c'est ta responsabilité de t'assurer que la mémoire à cette adresse est libérée.

            Tant que tu ne perds pas le pointeur, il n'y a pas de "fuite de mémoire assurée". Peut-être que le constructeur de Exemple libère la mémoire du pointeur qu'il reçoit (ça serait monstrueux, mais c'est "possible").

            Pour répondre à ta question, non, ce n'est pas une bonne pratique. Les allocations dynamiques sont lentes donc ce n'est pas non plus une optimisation. Je pense que c'est exactement comme @lmghs l'a dit et que c'est juste quelqu'un qui vient d'un langage orienté objet et qui ne comprend pas ce que fait vraiment le mot-clé new.

            • Partager sur Facebook
            • Partager sur Twitter
              16 septembre 2020 à 13:16:38

              Dans ce cas (l'écriture est séduisante) est-ce qu'une classe comme ci-dessous peut faire l'affaire  ?

              class Exemple
              {
              public:
              Exemple(t_Coordonnee *coord): mCoordonnee{ coord }
              {
              }
               
              private:
                  std::unique_ptr<t_Coordonnee> mCoordonnee;
              };

              Bien sûre, la documentation associée devra être claire sur la responsabilité de la classe Exemple (elle détient la propriété de l'objet t_Coordonnee).

              • Partager sur Facebook
              • Partager sur Twitter
                16 septembre 2020 à 18:32:39

                Je suis un développeur consciencieux, je pense a bien delete mes pointeurs :

                t_Coordonnee* coord = new t_Coordonnee;
                Exemple(coord);
                delete coord;

                Ouch !

                Le problème est que la doc n'est pas suffisant pour garantir qu'il n'y aura pas d'erreur.

                (Et les recommandations actuelles vont plutôt dans le sens qu'un raw pointer n'a pas l'ownership)

                • Partager sur Facebook
                • Partager sur Twitter
                  16 septembre 2020 à 18:57:06

                  >vont plutôt dans le sens qu'un raw pointer n'a pas l'ownership

                  Et donc, dans ce cas, Exemple doit "cloner" ces paramètre, mais 

                  Exemple exemple(new t_Coordonnee());

                  fera toujours des fuites mémoire (mais coder comme ça, ça mérite des baffes).

                  • Partager sur Facebook
                  • Partager sur Twitter
                  Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                    1 octobre 2020 à 17:16:32

                    Merci de toute vos réponses. Le problème est beaucoup plus clair. Je vais donc corriger le code en suivant vos conseils

                    Merci pour tout

                    • Partager sur Facebook
                    • Partager sur Twitter
                      2 octobre 2020 à 14:24:42

                      Solutions:

                      1) Ne pas recourir à l'allocation dynamique, et lui préférer l'allocation automatique quand c'est possible. Le recours à l'allocation dynamique ne se justifie que dans la mesure où il est difficile de déterminer la fin de vie de l'objet, ou un objet de très grande taille*. Dans une grande majorité des situation, la durée de vie de l'objet ne sort pas du scope, donc une allocation automatique sera parfaite.

                      Exemple ex{}; // Initialisation par defaut

                      Passer par des pointeurs intelligents, soit std::unique_ptr, soit le couple std::shared_ptr/std::weak_ptr. Le premier garantit l'unicité, le second permet le partage tout en garantissant la destruction a la disparition du dernier détenteur. Je te conseille d'utiliser au maximum le unique_ptr, d'abord parce qu'il est probablement mieux adapté à ton usage, ensuite parce qu'il est plus léger et plus simple (Les cas où le shared_ptr est indispensable sont assez rares).

                      auto ex = std::make_unique<Exemple>(/* paramètres du constructeur de Exemple */);
                      /* 
                        ou bien en typant tout:
                      
                      std::unique_ptr<Exemple> ex = std::make_unique<Exemple>(/* paramètres du constructeur de Exemple */);
                      */

                      Note que tu as une fonction make_shared qui fonctionne exactement comme make_unique et qui produit un shared_ptr au lieu d'un unique_ptr.

                      (*)  Les objets de grande taille: la première chose à savoir est qu'ils sont rarissimes, presque tout ce qui va être potentiellement gros (chaîne, tableau, arbre…) pourra quasiment toujours être stocké dans un conteneur plus ou moins standard, lequel est en principe, parfaitement capable de gérer sa mémoire comme un grand (RAII).  Il ne reste donc que de très gros struct, et là il faut vraiment des trucs monstrueux pour ne pas pouvoir utiliser l'allocation automatique, l'allocation automatique se fait sur la pile d'exécution qui fait quand même 1 Mo ou plus sur nos ordinateurs modernes.

                      -
                      Edité par int21h 2 octobre 2020 à 14:27:20

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Mettre à jour le MinGW Gcc sur Code::Blocks. Du code qui n'existe pas ne contient pas de bug

                      Eviter les fuites de mémoire, question méthode

                      × 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