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++

  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 508
    D�tails du profil
    Informations personnelles :
    Localisation : France, Paris (�le de France)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 1 508
    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 153
    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 153
    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

  8. #8
    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
    Citation Envoy� par julieng31 Voir le message
    Est-ce que je passe � c�t� de quelque chose ?
    Oui, tu passes � c�t� de pas mal de choses , plus que le dynamic_cast :
    • La programmation orient�e objet consiste � ce que ton container doit assurer l'int�grit� de ta structure interne. En exposant ta map interne, l'utilisateur qui utilise ton containeur pourrait modifier ta map sans modifier les vectors, ce qui serait inconsistant. Il faut au moins retourner ta map en const dans ce cas.
    • Le std::reference_wrapper n'est l� que pour r�soudre le probl�me de mettre une r�f�rence dans ta map de container. Ce serait bien de ne pas exposer son utilisation en dehors du container. Et tant qu'� faire, ne pas exposer le "unordered_map" en dehors de ton container. Peut-�tre qu'un jour, tu voudras remplacer ton std::unordered_map par un std::map ou par un vector de pairs, sans avoir � refaire tout le code de ce qui utilise ton container. Donc pour �a, il faut d�finir tes propres m�thodes d'acc�s � ton container, notamment par l'id.
    • Maintenant, on peut aborder le dynamic_cast. Premi�re chose � dire, �viter de l'utiliser . De mon point de vue, pour une question de performance et de compatibilit�. Les fonctions virtuelles peuvent tr�s bien s'occuper de ce boulot, cf l'exemple donn� ici. Tu cherches � am�liorer la robustesse de ton code avec les smart_pointeurs, mais le dynamic_cast va dans le sens contraire. Beaucoup de personnes disent que �a n'aurait jamais d� �tre int�gr� au C++ (coupl� avec la notion de RTTI).
    • Mais le dynamic_cast est plus simple � utiliser. Donc si tu veux l'utiliser, sache qu'il convertit des pointeurs en pointeurs. Donc il faut convertir ton std::reference_wrapper en pointeur. Pour �a il faut r�cup�rer la r�f�rence classique avec XXX.get(), puis prendre le pointeur correspondant avec &(XXX.get())


    Voil�, donc maintenant un peu de code.
    Pour les m�thodes de container :
    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
      /**
       * Throws if id not found
       */
      objectInterface& getObjectRefFromId(const std::string& id) const {
        return m_objects.at(id).get();
      }
     
      /**
       * Returns nullptr if id not found
       */
      objectInterface* getObjectPtrFromId(const std::string& id) const {
        auto objectIt = m_objects.find(id);
        if (objectIt == m_objects.end())
          return nullptr;
        return &objectIt->second.get();
      }
    Pour l'acc�s en dehors du container (en consid�rant le dynamic_cast) :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
        object1* myObject = dynamic_cast<object1*>(&(cont.getObjectRefFromId("id")));
        // OU
        object1* myObject = dynamic_cast<object1*>(cont.getObjectPtrFromId("id"));


    Citation Envoy� par dalfab Voir le message
    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!
    Attention, non, ce n'est pas le cas. Ca aurait �t� le cas avec des "std::vector<object1>", o� les objets sont stock�s c�te � c�te. Mais il n'y a pas de probl�me avec std::vector<std::unique_ptr<object1>>" o� chaque objet est allou� � une place fixe initiale et ne bouge pas.

  9. #9
    Membre confirm�
    Inscrit en
    Mars 2007
    Messages
    134
    D�tails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 134
    Par d�faut
    Merci encore dalfab et AbsoluteLogic pour vos retours !

    Peut �tre que je peux en dire un peu plus sur mon projet car vos avis me permettront peut �tre de mieux structurer mon code.

    En fait, je mod�lise des �quipement �lectroniques. Les vecteurs d'objets 1 & 2 que je repr�sente dans mon exemple simple est en fait une structure de donn�e assez large o� les �quipement contiennent des cartes qui contiennent des puces, qui contiennent des ports �lectroniques... Cette structure repr�sente la physique de ces �quipements (que je re�ois sous format XML). Cette structuration me permet simplement de d�finir, par exemple, quand un �quipement est en panne que toutes les puces � l'int�rieur sont en panne, ou de calculer la consommation �lectrique d'un �quipement en it�rant � l'int�rieur de la classe associ�e etc...

    Uniquement certains composants participent � la transmission de l'information. Ces composants, qui sont un �parpill�s dans la structure de donn�es, h�ritent tous d'une classe interface qui va me permettre pas exemple de r�cup�rer leur type (1 type par d�rivation de la classe interface). Les chemins d'information sont repr�sent�s par une s�rie de composants de ma structure de donn�es, chacun identifi� par un identifiant. Pour pouvoir "suivre"simplement un chemin d'information, j'utilise ma fameuse map. Du coup, pour propager un signal, je vais chercher les composants dans la map, je sais r�cup�rer leur type (via la classe d'interface) et je fais un dynamic_cast (tout en sachant d�j� que c'est possible et que le cast est possible).

    Un exemple de mod�le de donn�es :
    1. �quipement id = "eq 1"
    1.1 carte id = "carte 1.1"
    1.1.1 puce id = "puce 1.1.1" [h�rite de ma classe interface - type A]
    1.1.2 puce id = "puce 1.1.2" [h�rite de ma classe interface - type A]
    1.2 carte id "carte 1.2"
    2. �quipement id = "eq 2"
    2.1 carte id = "carte 2.1"
    2.1.1 puce 3 id = "puce 2.1.1" [h�rite de ma classe interface - type B]

    Un exemple de chemin :
    Chemin 1 = "puce 1.1.1" suivi de "puce 2.1.1" compos� d'un type A puis d'un type B

    Calcul :
    Chemin d�bute par "puce 1.1.1" -> v�rification qu'elle est dans la map -> v�rification par la classe interface que c'est le bon type -> dynamic_cast pour r�cup�rer les infos particuli�res � ce composant
    Chemin continue par "puce 2.1.1" -> v�rification qu'elle est dans la map -> v�rification par la classe interface que c'est le bon type -> dynamic_cast pour r�cup�rer les infos particuli�res � ce composant ... etc ...

    @AbsoluteLogic : mon exemple �tait un peu simple mais oui, j'ai bien des fonctions d'acc�s pour ne pas exposer la map.

    Si vous avez des suggestions, je les prends avec plaisir ! Merci d'avance ;-)

  10. #10
    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
    A mon tour de te poser une question : est-ce que tu vois ce qu'est un diagramme de classe ? Je ne me rends pas compte de ton exp�rience, de si c'est un projet professionnel, d'�cole, et perso.
    Mais ton projet se pr�te tr�s bien � la "programmation orient�e objet", dans la mesure o� chaque objet de ton programme repr�senterait des objets ou comportements concrets.
    Un diagramme de classe permettrait de tr�s bien faire appara�tre �a en le structurant. Sans parler de C++ � ce moment. Ensuite seulement on pourra discuter de comment le C++ peut repr�senter ce mod�le de mani�re robuste et efficace (s�rement sans dynamic_cast ).

  11. #11
    Expert confirm�
    Homme Profil pro
    D�veloppeur informatique
    Inscrit en
    F�vrier 2005
    Messages
    5 503
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 53
    Localisation : France, Val de Marne (�le de France)

    Informations professionnelles :
    Activit� : D�veloppeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : F�vrier 2005
    Messages : 5 503
    Par d�faut
    Le fait d'avoir � g�rer plusieurs classes de composant complexifie grandement le travail.
    Et, pour l'instant, je ne vois toujours rien qui justifie l'utilisation de plusieurs classes et pas une classe avec des champs au contenu diff�rent.

  12. #12
    Membre confirm�
    Inscrit en
    Mars 2007
    Messages
    134
    D�tails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 134
    Par d�faut
    Je suis plut�t habitu� � �crire du C++ pour faire du calcul donc avec des objets simples (plut�t des liens de composition). Pour des liens plus complexes, je fais de mon mieux... !

    J'ai tent� un exemple de diagramme. La partie statique (ie elle est charg�e au d�but et ne bouge pas) est mon data model. La partie dynamique peut �voluer et donne les chemins d'information.

    Ma difficult� est pour repr�senter les liens entre les signalPath qui contiennent une jeu d'identifiants parmi ceux qui sont disponibles parmi les "signalComponents".

    J'esp�re que mon diagramme n'est pas trop bancal...

    Nom : uml.png
Affichages : 258
Taille : 81,0 Ko

  13. #13
    Expert �minent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activit� : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par d�faut
    Salut,

    Pour ton probl�me de base (le choix entre std::unique_ptr et std::shared_ptr, tu peux partir sur le principe de base que:

    Il est pr�f�rable d'utiliser std::unique_ptr "par d�faut", dans la plupart des circonstances, sauf si tu n'as vraiment pas d'autre choix.

    Le fait est que, si tu peux en effet d�terminer sans l'ombre d'un doute qui est vraiment responsable de la dur�e de vie de la ressource associ�e � ton pointeur, qui a le droit "de vie et de mort" sur cette ressource et quand il va appliquer ce droit, cela va te simplifier �norm�ment la vie, vu qu'il n'y aura plus qu'un seul moment o� tu devras te poser la question de "dois-je liquider cette ressource ?".

    Dans 99 cas sur 100, il est possible -- quitte � r�organiser un peu son travail -- de d�terminer ces points pour �tre en mesure d'utiliser std::unique_ptr

    Pour ta question connexe:
    Citation Envoy� par julieng31 Voir le message
    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 ?
    De mani�re g�n�rale, le transtypage, quel qu'il soit, ne devrait JAMAIS �tre utilis�, et c'est ** forc�ment ** le cas de dynamic_cast.

    C'est m�me encore pire en ce qui concerne le dynamic_cast (il y a quelques circonstances dans lesquelles un static_cast peut �ventuellement �tre envisag�) que cela va t'inciter � mettre en place une logique qui brise litt�ralement le deuxi�me principe SOLID : l'OCP.

    L'OCP ( Open / Close Principle, ou, si tu pr�f�res en fran�ais, Principe Ouvert / Ferm�) stiupule en effet que tu ne devrais pas avoir � modifier un comportement qui a �t� valid� -- sauf pour le corriger ou l'am�liorer -- afin de te permettre d'ajouter une nouvelle fonctionnalit�.

    Or, si tu as une hi�rarchie de classes "classique", avec une classe de base et plusieurs classes d�riv�es, dans le style de
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Base{
    public:
        virtual ~Base();
        /* le reste n'a que peu d'importance */
     
    };
    class Derivee : public Base{
        /* le reste n'a que peu d'importance */
    };
    class AutreDerivee : public Base{
        /* le reste n'a que peu d'importance */
    };
    et que tu en viens,dans une foncion quelconque � devoir �crire un code proche de
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
     void foo( Base * ptrBase){
        if(dynamic_cast<Derivee*>(ptrBase) ){
            /* on fait quelque chose */
        }else if if(dynamic_cast<AutreDerivee*>(ptrBase) ){
            /* on fait autre chose */
       }
    }
    cela va t'obliger, si tu souhaite "un jour" ajouter une classe d�riv�e suppl�mentaire, � reprendre le code de cette fonction afin d'y ajouter la possibilit� de
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    else if (dynamic_cast<NouvelleDerivee*> (ptrBase) ){
        /* on fait encore autre chose */
    }
    Le probl�me, c'est que, du coup, tu dois modifier le comportement de la fonction foo, qui est pourtant valid� en l'�tat (tu as pu d�montrer que la fonction fait effectivement ce que l'on attend de sa part) simplement pour �tre en mesure d'ajouter cette nouvelle classe.

    Et pire encore : comme cette technique fonctionne ind�niablement, on peut s'attendre � ce que tu l'aie utilis�e dans dix, quinze ou peut-�tre m�me une centaine de fonctions diff�rentes, en fonction de la complexit� de ton projet.

    Tu devras donc aller modifier ton code dans chacune des fonctions utilisant cette possibilit� pour pouvoir rajouter ta nouvelle classe. Et tu vas forc�ment en oubliier, ce qui provoquera des bugs du genre "pourquoi quand j'appelle telle fonction (que tu as modifi�e), tout fonctionne bien, et quand j'appelle telle autre (que tu as oubli�e), ca ne donne plus le r�sultat attendu?"

    Le principe de base est donc que, si tu as �t� "assez b�te" que pour oublier le type r�el d'une donn�e, tu dois baser ta logique sur le type "pour lequel ta donn�e passe".

    Il y a donc une r�gle absolue pour ce qui concerne l'h�ritage publique : le LSP (Liskov Substitution Principle ou principe de substitution de Liskov, le L de SOLID) doit imp�rativement �tre respect� et va servir de "GO / NO GO" conceptuel dans le choix d'appliquer ou non un h�ritage.

    Afin de respecter ce principe, il y a trois points � v�rifier:
    1. les pr�conditions ne peuvent pas �tre renforc�es (plus strictes) dans les classes d�riv�es. C'est la raison pour laquelle on ne pourra pas faire h�riter la classe Carre de la classe Rectangle (il y a une pr�condition dans Carr�: longueur == largeur, qui est plus ferme que ... l'absence de toute pr�condition dans la classe Rectangle)
    2. les postconditions ne peuvent pas �tre all�g�e dans les classes d�riv�es. c'est la raison pour laquelle on ne pourra pas faire h�riter la classe Rectangle de la classe Carre (il y a une postcondition dans Carre : longueur == largeur, qui n'existe simplement pas dans la classe Rectangle).
    3. L'ensemble des invariants de la classe de base est respect� dans la classe d�riv�e. Par invariant, il faut comprendre
      • l'ensemble des fonctions, qu'elles soient publiques ou non, valides pour la classe de base doivent imp�rativement �tre valides pour la classe d�riv�e
      • l'ensemble des donn�es qui ont du sens pour la classe de base doivent avoir du sens pour la classe d�riv�e
      • l'ensemble des conditions permettant � la classe de base de "fonctionner correcttement" doivent �tre valides pour la classe d�riv�e

    En d'autre termes, si tu ne peux d�j� pas utiliser ta classe d�riv�e exactement comme s'il s'agissait d'une instance de la classe de base, alors, c'est que tu ne peux pas tracer la relation d'h�ritage entre les deux classes. Et ca, c'est un "GO / NO GO" conceptuel.

    C'est � dire que, peu importe ce que l'on a pu te dire � l'�cole (qu'un carr� est un rectangle, par exemple), peu importe que le langage autorise ou non la relation d'h�ritage (dans le cas d'h�ritage multiple, par exemple), peu importe ce que te dira le compilateur (qui ne fait que respecter les r�gles de base impos�es par le langage), si les r�gles que je viens de citer ne sont pas respect�es AVANT M�ME D'ECRIRE LA PREMIERE LIGNE DU CODE qui fera h�riter ta classe d�riv�e de ta classe de base, c'est que tu ne peux pas prendre la d�cision de faire h�riter ta classe d�riv�e de ta classe de base. Et tu dois donc trouver une autre solution.

    Cependant, il existe effectivement des circonstances dans lesquelles on pourrait souhaiter �tre en mesure de manipuler une instance de la classe d�riv�e en tant ... que cette classe d�riv�e elle-m�me, malgr� le fait qu'on la connaisse comme �tant une instance "de la classe de base".

    Dans ce genre de cas, la solution � envisager va passer par ce que l'on appelle le double dispatch. L'id�e de base de ce concept est que, m�me si nous ne connaissons une donn�e que comme "�tant une instance de la classe de base", la donn�e en elle-m�me sait encore tr�s bien quel est son type r�el. On va donc utiliser un comportement de la classe de base qui permette � la classe d�riv�e de se transmettre � ... deuxi�me comportement, en tant qu'instance de la classe d�riv�e.

    Et comme ce deuxi�me comportement saura pertinemment quel les le type (r�el, cette fois ci) de la donn�e, il pourra la manipuler en tant que telle.

    Ce concept peut prendre la forme d'un patron de conception (design pattern) appel� "visiteur", qui pourrait ressembler � ceci:
    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
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    /* D'un coté, nous avons une classe "Visiteur", qui peut être dérivé en différent types de visiteur "concret" et 
     * qui expose des comportements spécifiques pour chacune des classes dérivée
     * 
     * Pour satisfaire le compilateur, il faut dresser la liste des classes susceptibles d'être manipulées ;-)
     */
    class Base; // la classe de base
    class Derivee; // une premiere classe dérivée
    class AutreDerivee; // une deuxième classe dérivée
    class NouvelleDerivee; // la classe dérivée que l'on ajoute plus tard (on ajoute cette ligne lors de l'ajout de la classe :D )
    /* ... on peut en avoir tout plein, bien sur */
    /* Cela nous permet de déclarer des fonctions virtuelles prenant une référence sur ces différents types, sous la forme de */
    class Visiteur {
    public:
        /* dans un premier temps, on ne sait pas ce qu'il fera de ces données, on va donc les déclarer comme fonctions virutelles pures */
        virtual void visit( Derivee /*const*/ & d) = 0; 
        virtual void visit(AutreDerivee /* const*/ & d) =0;
        virtual void visit(NouvelleDerivee /* const*/ & d) =0;
        /* on fera bien sur cela pour toutes les classes dérivées */
    };
     
    /* Et, d'un autre coté, nous aurons donc la hiérarchie de classes  qui nous intéresse qui exposera un comportement
     * permettant ... d'accepter le visiteur.  C'est ce comportement qui sera défini pour chacune des classes dérivées, sous la forme
     * de
     */
    class Base{
    public:
        /* Les hierachies de classes ont, typiquement, sémantique d'entité.  On ne veut en aucun cas pouvoir utiliser le constructeur de copie
         * ou l'opérateur d'affectation
         */
        Base(Base const &) = delete;
        Base & operator= (Base const &) = delete;
        /* C++11 est arrivé avec les notions d'affectation et de copie par déplacement,  on ne veut pas d'avantage pouvoir y avoir recours */
        Base(Base && ) = delete;
        Base & operator=(Base && ) = delete;
        /* on voudra par contre sans doute pouvoir détruire n'importe quelle instance d'une classe dérivée alors que nous 
         * la connaissons comme étant une instance de la classe de base
         */
        virtual ~Base(); = default;
        /* La magie du visiteur : le comportement accept, que nous devrons définir pour chacune des classes dérivées.
         * Comme nous ne pouvons pas fournir de comportement adéquat pour la classe de base, nous en faisons une fonction
         * virtuelle pure
         */
        virtual void accept(Visiteur /* const */ & ) = 0;
    };
    /* dans les classes dérivées, nous implémentons le comportement accept, toujours de la même manière : en appelant 
     * le comportement visit du visiteur, et en lui transmettant la donée elle_même
     */
    class Derivee : public Base{
    public:
        void accept(Visitor /* const*/  & v) override{
            v.visit(*this);
        }
        /* Il peut y avoir d'autres trucs ici */
    };
    class AutreDerivee : public Base{
    public:
        void accept(Visitor /* const*/  & v) override{
            v.visit(*this);
        }
        /* Il peut y avoir d'autres trucs ici */
    };
    class NouvelleDerivee : public Base{
    public:
        void accept(Visitor /* const*/  & v) override{
            v.visit(*this);
        }
        /* Il peut y avoir d'autres trucs ici */
    };
    /* et bien sur, nous le ferons pour toutes les classes dérivées. */
     
    /* Enfin, chaque fois que nous voudrons faire "quelque chose de spécial" avec nos données 
     * (qui sont, en réalité, des instances des classes dérivées, mais que nous connaissons comme étant
     * des instances de la classe de base), nous pourrons créer un visiteur (concret, celui-i) pour lequel
     * nous définissons le comportement visit, adapté à chacun des types des classes dérivées
     */
     class VisiteurConcret : public Visiteur {
    public:
        /* dans un premier temps, on ne sait pas ce qu'il fera de ces données, on va donc les déclarer comme fonctions virutelles pures */
        void visit( Derivee /*const*/ & d) override{
            /* je sais manipuler une instance de la classe Derivee, que dois-je faire ici? */
        } 
        void visit(AutreDerivee /* const*/ & d) override{
            /* je sais manipuler une instance de la classe AutreDerivee, que dois-je faire ici? */
        } 
        void visit(NouvelleDerivee /* const*/ & d) override{
            /* je sais manipuler une instance de la classe NouvelleDerivee, que dois-je faire ici? */
        } 
        /* on fera bien sur cela pour toutes les classes dérivées */
    };
     
    /* Tout cela nous permettra de travailler "facilement" dans la plupart des situations, par exemple, une fonction comme celle ci
     */
    int main(){
        std::vector<std::unique_ptr<Base>> datas; // C'est dans ce tableau que je maintiens l'ensemble des données
        /* je remplis mon tableau d'une manière ou d'une autre */
        /* puis vient le moment où je veux les utiliser
         * je crée donc l'instance de mon visiteur concret */
        VisiteurConcret visiteur;
        /* et, pour chacune des données que je trouve dans le tableau, */
        for(auto & ptr : datas){
            /* je demande à la donnée d'accepter mon visiteur */
           ptr->accept(visiteur);
        }
    }
    L'�norme avantage de la technique, c'est que, si je d�cide "un jour" de rajouter une classe d�riv�e, je devrai -- bien sur -- ajouter le comportement visit(la_nouvelle_classe) dans ma classe Visiteur et impl�menter ce comportement dans l'ensemble des visiteurs concr�ts que j'utilise, MAIS je ne dois absolument pas toucher aux diff�rents comportements que j'ai d�j� mis en place pour les autres classes (hormis, bien sur, le fait de pr�voir la cr�ation des instances de cette nouvelle classe). Et donc, je r�duis consid�rablement le risque de "tout casser", vu que je n'ai plus rien � modifier...

    Alors, bien sur ... Cette technique pr�sente quelques inconv�nients. Dont celui de devoir impl�menter le comportement de cette nouvelle fonction visit dans l'ensemble des visiteurs concr�ts, ce qui aura sans doute un impact sur l'ABI. Pour faire simple, si tu d�veloppe une DLL, l'ajout d'une nouvelle classe t'obligera � utiliser la nouvelle verison de cette DLL sous peine d'avoir peut �tre quelques soucis.

    Cependant, cela peut apporter une telle s�r�nit� dans ton d�veloppement, et, surtout, te faire gagner tellement de temps

    Ah, et un petit truc en plus : � partir du moment o� tu envisages d'utiliser l'h�ritage, tu devrais envisager tes classes sur base de leur comportements (des fonctions, virtuelles ou non, auquellles tu estimes devoir pouvoir acc�der), plut�t que sur base de leurs donn�es.

    Car ce sont les comportements qui permettent le mieux de d�duire le type de l'objet auquel on a afffaire ( "Si mon objet marche comme un canard, qu'il cancanne comme un canard et qu'il nage comme un canard, il est tr�s possible que j'ai affaire � un canard ). Les donn�es qui composent l'objet n'�tant en d�finitive l� que pour ... permettre aux diff�rents comportements de fournir le r�sultat attendu.
    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

  14. #14
    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
    Citation Envoy� par julieng31 Voir le message
    J'ai tent� un exemple de diagramme.
    Bonjour Julien, le diagramme de classe nous aiderait � donner des r�ponses un peu plus �clair�es � tes questions, mais je n'ai quand m�me pas la motivations de choisir/r�diger la structure C++ pour tous les composants. Donc en tout cas je suis dispo pour te r�pondre si tu as des doutes sur des points pr�cis. Et typiquement si tu ne vois pas comment ne pas utiliser le dynamic_cast dans un certain cas concret (si possible avec le contexte autour, donc notamment le diagramme), tu peux nous faire part de ce cas qui servira d'exemple.

    Une petite remarque concernant ton diagramme quand m�me, il y a beaucoup de "identifier". L'id�e est de le mettre dans une classe m�re, sans le remettre dans les classes filles s'il s'agit du m�me type d'identifier. Et s'il y a diff�rents types d'identifiers, il faut les nommer diff�remment.

    Voil�, on s'�loigne un peu du titre de la discussion, mais j'imagine tant pis ^^

  15. #15
    Membre confirm�
    Inscrit en
    Mars 2007
    Messages
    134
    D�tails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 134
    Par d�faut
    C'est vrai que cela d�passe un peu la question initiale mais cela m'a permis d'apprendre qu'il faut �viter le dynamic_cast et de r�fl�chir � nouveau sur la structuration des mes donn�es ;-) Merci pour vos conseils pr�cis et pr�cieux !

    Je clos le post mais continue d'am�liorer mon approche !

  16. #16
    Expert confirm�
    Homme Profil pro
    D�veloppeur informatique
    Inscrit en
    F�vrier 2005
    Messages
    5 503
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 53
    Localisation : France, Val de Marne (�le de France)

    Informations professionnelles :
    Activit� : D�veloppeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : F�vrier 2005
    Messages : 5 503
    Par d�faut
    Pourquoi "Connexion" et "Plug" devraient �tre des classes s�urs ???
    Pourquoi ne pas les mettre dans 2 listes distinctes ? (la seul contrainte que je vois, c'est qu'elles ne doivent pas avoir d'identifiant commun)

  17. #17
    Membre confirm�
    Inscrit en
    Mars 2007
    Messages
    134
    D�tails du profil
    Informations forums :
    Inscription : Mars 2007
    Messages : 134
    Par d�faut
    En effet, l'id�e �tait de g�rer d'une part l'unicit� d'identifiants (ce qui expliquait le choix d'une map) mais aussi d'avoir "sous la main" tous les composants qui participent � chemin d'information. J'y r�fl�chissais, c'est peut �tre cela qui m'am�ne � devoir ensuite revenir aux classes filles car je ne manipule que la classe interface.

    Je pourrais avoir des listes "typ�es" (par composant) et avoir un accesseur qui me fournirait un �l�ment d'un type donn� s'il existe, comme �a je ne "perds" pas le type de la classe fille. �a me fait juste une fonction d'acc�s par type mais ils ne sont pas trop nombreux.

  18. #18
    Membre Expert
    Femme Profil pro
    ..
    Inscrit en
    D�cembre 2019
    Messages
    679
    D�tails du profil
    Informations personnelles :
    Sexe : Femme
    �ge : 95
    Localisation : Autre

    Informations professionnelles :
    Activit� : ..

    Informations forums :
    Inscription : D�cembre 2019
    Messages : 679
    Par d�faut
    Salut tout le monde,

    Citation Envoy� par julieng31 Voir le message
    ...
    Ton message m'�voque le protocole SNMP (avec ses OID, MIB).
    Si tu ne connais pas, renseigne-toi dessus, �a peut �tre une bonne source d'inspiration. En plus, il y a de bonnes biblioth�ques et outils d�j� existants.
    Maintenant, utiliser std::map, un graphe binaire pour mod�liser ton r�seau me semble un peu limite. Personnellement, je vois DOM comme un meilleur candidat, surtout qu'� terme il y aura probablement un besoin d'interop�rabilit�. Donc �a, plus la forte ressemblance avec SNMP, il y a de quoi faire quelque chose de bien.

    Bon courage pour ton projet, il est int�ressant.

+ 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