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 :

unique_prt, shared_ptr ou weak_ptr ?


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Inscrit en
    Mars 2007
    Messages
    134
    Détails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 134
    Par défaut unique_prt, shared_ptr ou weak_ptr ?
    Bonjour,

    Pour un de mes projets, j'ai une structure avec diverses listes d'objects. Cette structure "gère" ces objets. Pour des raisons de calcul, j'ai besoin d'accéder à tous ces objects de la même manière donc les objects héritent d'une classe interface et ils ont stockés dans une "map" qui contient un pointeur vers chacun des objects.

    Mon approche marche mais n'est pas très "C++ moderne". J'aimerais remplacer les pointeurs par des "smart pointers" pour une meilleure gestion de la mémoire. Par contre, je ne les connais pas très bien et je me demande ce qu'il faut que j'utilise, j'hésite entre :
    - des shared_pointers mais la "map" est pour moi juste un moyen d'accéder aux objects et n'est pas censée les "posséder" (au sens ownership)
    - des unique_pointers pour le stockage des objects et des weak_pointers dans la "map" d'accès.

    Pourriez-vous m'aider s'il vous plaît ? Merci d'avance !

    Pour être plus concret, voisi un "pseudo code" :

    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
    50
    51
    52
    class objectInterface {
      public:
      objectInterface() {}
      virtual ~objectInterface() {}
      std::string getId() const {return m_id};
     
      private:
      std::string m_id;
    }
     
    class object1 : public objectInterface {
      public:
      object1() : objectInterface() {}
      void loadCharacteristics(/*XML file*/) {/*load characteristics*/}
    }
     
    class object2 : public objectInterface {
      public:
      object2() : objectInterface() {}
      void loadCharacteristics(/*XML file*/) {/*load characteristics*/}
    }
     
    class container {
      public:
      container() {}
      void loadObjects() {
         while(/*read an XML file*/) {
           if(/*object1*/) {
             object1 newObject;
             newObject.loadCharacteristics(/*XML file*/);
             m_myObjects1.emplace_back(newObject);
             m_objects.emplace(newObject.getId(), &newObject);
           }
           else if(/*object2*/) {
             /* Idem object 1 */
           }
         }
      }
      void clearObjects() {
        m_myObjects1.clear();
        m_myObjects2.clear();
        for(auto & [id, obj]:m_objects) {
          if(obj!=nullptr)
            delete obj;
        }
      }
     
      private:
      std::vector<object1> m_myObjects1;  // shared ou unique ptr ?
      std::vector<object2> m_myObjects2;  // shared ou unique ptr ?
      std::unordered_map<std::string, objectInterface *> m_objects;  // accès par shared_ptr ou weak_ptr ?
    }

  2. #2
    Membre Expert
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    1 501
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 1 501
    Par défaut
    Salut,

    Pose toi les bonnes questions:
    Qui est propriétaire ?
    Une seule classe ? ==> std::unique_ptr.
    Plusieurs classes ==> std::shared_ptr.
    (je n'ai pas assez de connaissances pour répondre ce qui concerne std::weak_ptr).

    Quand aux accès, contentes-toi d'écrire des accesseurs qui renvoient une référence.

    Par défaut, je partirais sur std::unique_ptr, voir selon le comportement attendu de ton conteneur, faire un héritage privé à partir de std::map (ainsi il sera compatible avec la STL).

  3. #3
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 147
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 147
    Billets dans le blog
    4
    Par défaut
    weak_ptr c'est une soft référence vers un shared_ptr donc le mentionner avec unique_ptr est totalement faux.

    une "map" qui contient un pointeur vers chacun des objects
    la "map" est pour moi juste un moyen d'accéder aux objects et n'est pas censée les "posséder" (au sens ownership)
    et qui les possède alors si c'est pas la map ?
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  4. #4
    Membre expérimenté
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juillet 2018
    Messages
    104
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Tarn (Midi Pyrénées)

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

    Informations forums :
    Inscription : Juillet 2018
    Messages : 104
    Par défaut
    Bonjour Julieng31,


    Il faut se dire que le shared_ptr est plus coûteux qu'un unique_ptr en terme de performance et de mémoire. En effet :
    • un unique_ptr gère le cycle de vie d'un objet de la même manière que si tu faisais toi-même le new/delete. Je pense même qu'un programme compilé en mode optimisé serait équivalent.
    • un shared_ptr gère un compteur de référence, et même si je ne sais pas exactement comment il est implémenté, il doit en toute logique créer une "indirection" du cycle de vie pour la rendre plus flexible. (J'utilise le terme "indirection" de manière volontairement floue, pour vraiment avoir les détails, je te laisse chercher sur internet)


    Donc il vaut mieux utiliser un shared_ptr seulement s'il apporte réellement un intérêt, qui serait, de manière générale, dans les cas où tu ne sais pas qui sera le dernier à manipuler tes objets. Même si ça revient au même que ce qu'à dit Deedolith, je formule ça différemment parce que je pense au cas suivant : Même si tes objets sont initialement stockés dans ton "container", est-ce que tu veux permettre aux utilisateur de conserver ces objets après la destruction du containeur ?

    Bon, maintenant supposons qu'on veuille voir le cas le plus classique, où l'utilisateur n'a pas besoin des objets quand le container est détruit. Je partirais donc sur le fait d'utiliser des unique_ptrs. Mais ta proposition "des unique_pointers pour le stockage des objects et des weak_pointers dans la map d'accès" ne marchera pas, parce qu'un weak_ptr est obligatoirement associé à un shared_ptr. Et cela pour notamment deux raisons :
    • parce que les unique_ptrs, comme je disais, sont trop "simples" pour pouvoir être attachés à des weak_ptr
    • parce que tu n'as pas le droit d'accéder à un weak_ptr directement. A la place, tu dois le "locker" pour créer un shared_ptr (cf l'exemple dans la doc)


    Maintenant, si on met des unique_ptr dans les vectors, qu'est-ce qu'on met dans la map ?
    A vrai dire, moi, pour plus de simplicité, je mettrais simplement des pointeurs
    Et la solution plus verbeuse mais surement plus "propre", c'est d'utiliser des std::reference_wrapper. Mais je n'ai pas d'expérience avec eux.
    Malgré mon peu d'expérience, je vais quand même partir sur ça, puisque mon post a quand même pour but d'être "éducatif".
    Les gens n'aiment pas les pointeurs parce que le fait que ça puisse changer de pointage et que ça puisse être nul rend la gestion de mémoire moins sûre.
    En revanche, les "références" (&) sont plus sûres parqu'elles évitent ces deux caractéristiques.
    Mais les containers comme ta map, eux, ont besoin de ces caractéristiques pour pouvoir manipuler leur contenu.
    C'est en quelque sorte pour ça qu'ont été créés les std::reference_wrapper : avec la sécurité des références mais pouvoir être utilisé dans les containers.
    Et les références fonctionnent bien dans ton cas parce que:
    • les objets stockés ne seront jamais nuls
    • l'objet associé à un id ne changera jamais
    • les cycles de vie des objets qu'ils pointent est assuré par ta classe container


    Bon, voilà, je t'offre donc le code "propre" directement pour t'aider à rentrer plus rapidemment dedans, mais si tu pars sur ça, il faudra quand même que tu apprennes les méthodes de manipulation de ces objets.
    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
    50
    51
    52
    53
    54
    #include <unordered_map>
    #include <vector>
    #include <memory>
    #include <functional>
     
    class objectInterface {
      public:
      objectInterface() {}
      virtual ~objectInterface() {}
      std::string getId() const {return m_id;};
     
      private:
      std::string m_id;
    };
     
    class object1 : public objectInterface {
      public:
      object1() : objectInterface() {}
      void loadCharacteristics(/*XML file*/) {/*load characteristics*/}
    };
     
    class object2 : public objectInterface {
      public:
      object2() : objectInterface() {}
      void loadCharacteristics(/*XML file*/) {/*load characteristics*/}
    };
     
    class container {
      public:
      container() {}
      void loadObjects() {
         while(/*read an XML file*/) {
           if(/*object1*/) {
             auto newObject = std::make_unique<object1>();
             newObject->loadCharacteristics(/*XML file*/);
             m_objects.emplace(newObject->getId(), std::ref(*newObject));
             m_myObjects1.emplace_back(std::move(newObject));
           }
           else if(/*object2*/) {
             /* Idem object 1 */
           }
         }
      }
      void clearObjects() {
        m_myObjects1.clear();
        m_myObjects2.clear();
        m_objects.clear();
      }
     
      private:
      std::vector<std::unique_ptr<object1>> m_myObjects1;  // shared ou unique ptr ?
      std::vector<std::unique_ptr<object2>> m_myObjects2;  // shared ou unique ptr ?
      std::unordered_map<std::string, std::reference_wrapper<objectInterface>> m_objects;  // accès par shared_ptr ou weak_ptr ?
    };
    Ah, et au fait, j'ai proposé tout ça pour suivre ton idée initiale. En fonction de ce que tu as réellement besoin, tu aurais pu te contenter de stocker plus simplement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::unordered_map<std::string, std::unique_ptr<objectInterface>> m_objects;
    ou
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    std::vector<std::unique_ptr<objectInterface>> m_myObjects; // Pour itérer plus efficacement sur les valeurs
    std::unordered_map<std::string, std::reference_wrapper<objectInterface>> m_objectsMap;

  5. #5
    Membre confirmé
    Inscrit en
    Mars 2007
    Messages
    134
    Détails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 134
    Par défaut
    Bonjour et merci pour vos réponses !

    deedolith, AbsoluteLogic, en effet, les uniques pointers étaient ma solution "initiale" mais j'étais embêté avec le contenu de ma "map". Je n'ai mis qu'un petit exemple mais j'ai besoin d'avoir cette double structuration vecteur conteneur & map pour des besoins de calcul mais aussi de représentation "matérielle/physique" des objets qui sont du "hardware". Ce qui m'a amené aux unique pointers est en effet que le cycle de vie est porté par le vecteur. La map ne permet que d'accéder aux données plus efficacement (d'ailleurs dans la fonction clearObjects, les 2 sont "nettoyés" en même temps.

    Je n'avais pas compris que les weak pointers ne fonctionnaient que basé sur des shared pointers, en effet, mon idée ne marche pas !

    AbsoluteLogic, je ne connaissais pas les reference wrapper, je vais regarder, merci ! Est-ce que le fait d'avoir des objets représentés hérités d'une classe interface et de devoir dans mon code "dynamic cast" de la classe mère vers la classe fille ne posera pas de problème avec ces reference wrapper ?

  6. #6
    Membre confirmé
    Inscrit en
    Mars 2007
    Messages
    134
    Détails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 134
    Par défaut
    Je viens de tester et j'ai bien le soucis j'évoquais. La ligne suivante ne compile pas :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    object1 * myObject = dynamic_cast<object1 *>(container.getObjects().at("id"));
    avec:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    std::unordered_map<std::string, std::reference_wrapper<objectInterface>> & container::getObjects() {
      return m_objects;
    }
    Est-ce que je passe à côté de quelque chose ?

  7. #7
    Expert confirmé
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 599
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    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 599
    Par défaut
    Un reference_wrapper ça fonctionne comme une référence. Sa particularité c'est que contrairement aux références on peut les mettre dans des collections. Donc comme toute référence tu peux faire un cast vers une référence dans la hierarchie.
    Mais tu fais un cast de référence vers pointeur! il faut prendre l'adresse puis caster en pointeur, ou caster vers une référence.

    Et attention en prenant un vector comme responsable de tes entités. Toute modification du vector va casser toute référence ou pointeur sur ses éléments!
    Si on considère que tu ajoutes/retranche dynamiquement des objets si tu ne le fais qu'aux extrémités, tu peux utiliser un std::deque à la place du std::vector<>, ça s'utilise pareil c'est un petit peu moins performant (en vitesse et en taille).
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    private:
        std::deque<std::unique_ptr<object1>> m_myObjects1;  // 
        std::deque<std::unique_ptr<object2>> m_myObjects2;  //
        std::unordered_map<std::string, std::reference_wrapper<objectInterface>> m_objects;
    };
     
          object1 * myObject = dynamic_cast<object1*>(   &   container.getObjects().at("id"));
    Mais le dynamic_cast est rarement une bonne solution

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

Discussions similaires

  1. Arbre shared_ptr et weak_ptr
    Par darkman19320 dans le forum C++
    Réponses: 7
    Dernier message: 30/08/2017, 22h07
  2. Passage de C++ à Java : Shared_ptr et Weak_ptr
    Par malaboss dans le forum Général Java
    Réponses: 5
    Dernier message: 25/06/2012, 18h40
  3. Réponses: 1
    Dernier message: 02/10/2011, 12h56
  4. [BOOST] shared_ptr et pointer C
    Par zdra dans le forum Bibliothèques
    Réponses: 7
    Dernier message: 08/05/2005, 14h15

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