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 :

Composition, références/pointeurs et posession


Sujet :

C++

  1. #1
    Membre averti Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Points : 341
    Points
    341
    Par défaut Composition, références/pointeurs et posession
    Salut à toutes et à tous !

    Je reviens vous embêter un chouïa...

    Quand j'ai commencé le C++ (il y a ... houla, au moins 4 mois ) j'ai suffisamment lu des phrases du style "utiliser les références partout où vous devez, et les pointeurs quand vous pouvez pas faire autrement" pour tenter d'appliquer ce principe à la lettre. Donc j'ai mis des références partout où j'avais besoin de polymorphisme : dans les constructeurs, dans les données membres... Je partais du seul principe que pour peu que l'instance existe déjà, je peux utiliser une référence sur son type ancêtre pour activer le polymorphisme, et basta.
    Et ça a marché un temps.
    J'avais bien fait un ou deux new à un moment, mais comme je ne savais pas exactement comment gérer la mémoire, que je préfère ne pas faire les choses plutôt que de les faire salement, j'ai choisi de mettre aucun delete, en attendant que ça me retombe sur la tronche pour m'en occuper sagement/proprement. ça n'a pas tardé, on est en plein dedans.

    Donc là j'ai commencé à arpenter pas mal de sites sur les références, les pointeurs, les pointeurs intelligents etc. J'ai commencé à voir que mon principe d'utilisation (objet déjà instancié + polymorphisme requis = reference) était pas si bon que ça, qu'il y avait aussi un truc appelé "ownership", qu'on finit bien par avoir besoin de créer un objet dynamiquement, et qu'il faut bien le détruire à un moment où à un autre et que la destruction est un grand pouvoir impliquant de graaandes responsabilités.

    J'ai aussi lu que si une fonction utilisait une référence sur un objet, elle "renonçait" à sa propriété. C'est le point qui précisément m'inquiète. Parce que j'ai fait de la composition en folie, en utilisant des références sur les composantes. Je me rend bien compte que par le jeu des références il est bien maladroit de détruire les membres-composantes via le destructeur de l'objet-composite.

    Typiquement, dans le code suivant, les membres/composants peuvent être de plusieurs types, donc j'avais besoin de polymorphisme. D'un autre côté, au moment de construire un objet A, je savais exactement de quelle classe héritant de J j'avais besoin. Donc j'ai codé ça avec des références sur J, en laissant à un builder le soin de construire dynamiquement les composantes c1 et c2 selon les options que je lui donne, puis de les filer au constructeur de A.

    Seul souci : qui détruit ces objets c1 et c2 au final ? Je pense que c'est à A de les détruire, parce que sortis de A, c1 et c2 n'ont aucun sens. Mais si c'est le cas, va falloir que je modifie toutes mes interfaces/tests/implémentations ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
     
    class A :{
        public:
            A(J & c1, J & c2);
            virtual ~A();
     
        protected:
        private:
            J& m_c1;
            J& m_c2;
     
    };
    Donc est-ce que je remplace toutes les références sur les composantes par des pointeurs ?
    Merci d'avance de votre réponse,
    Bien cordialement,
    Le débutant, lui, ignore qu'il ignore à ce point, il est fier de ses premiers succès, bien plus qu'il n'est conscient de l'étendue de ce qu'il ne sait pas, dès qu'il progresse en revanche, dès que s'accroît ce qu'il sait, il commence à saisir tout ce qui manque encore à son savoir. Qui sait peu ignore aussi très peu. [Roger Pol-Droit]
    Github
    Mon tout premier projet: une bibliothèque de simulation de génétique des populations

  2. #2
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Si c'est à A de les détruire, alors tu vas devoir remplacer tes références par des pointeurs intelligents (notamment std::unique_ptr<>).
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  3. #3
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Hello,

    Citation Envoyé par Seabirds Voir le message
    Seul souci : qui détruit ces objets c1 et c2 au final ? Je pense que c'est à A de les détruire, parce que sortis de A, c1 et c2 n'ont aucun sens. Mais si c'est le cas, va falloir que je modifie toutes mes interfaces/tests/implémentations ? ,
    Vu ton code, non, A utilise c1 et c2 sans se soucier de leur durée de vie. Celui que crée un A (et qui passe des refs c1 / c2) devra gérer la destruction de c1 / c2 (et s'assurer qu'ils vivent assez longtemps.

    Si A possède c1 / c2, ton code devrait plutôt ressembler à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct A {
       A(std::unique_ptr<J>&& c1, std::unique_ptr<J>&& c2):
          m_c1(std::move(c1)),
          m_c2(std::move(c2))
       { }
     
       std::unique_ptr<J> m_c1;
       std::unique_ptr<J> m_c2;
    };
    Ou
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    struct J {
       J(int i, float f) { /*...*/ }
    };
     
    struct A {
       A(int i1, float f1, int i2, float f2):
          m_c1(std::make_unique<J>(i1, f1)),
          m_c2(std::make_unique<J>(i2, f2))
       { }
     
       std::unique_ptr<J> m_c1;
       std::unique_ptr<J> m_c2;
    };

  4. #4
    Membre averti Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Points : 341
    Points
    341
    Par défaut
    Il n'y a pas de smiley "dévotion absolue"...

    Mille merci pour vos réponses, c'est exactement ce que je cherchais.
    Bon, en route pour les modifications
    Le débutant, lui, ignore qu'il ignore à ce point, il est fier de ses premiers succès, bien plus qu'il n'est conscient de l'étendue de ce qu'il ne sait pas, dès qu'il progresse en revanche, dès que s'accroît ce qu'il sait, il commence à saisir tout ce qui manque encore à son savoir. Qui sait peu ignore aussi très peu. [Roger Pol-Droit]
    Github
    Mon tout premier projet: une bibliothèque de simulation de génétique des populations

  5. #5
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    Si A possède c1 / c2, ton code devrait plutôt ressembler à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct A {
       A(std::unique_ptr<J>&& c1, std::unique_ptr<J>&& c2):
          m_c1(std::move(c1)),
          m_c2(std::move(c2))
       { }
     
       std::unique_ptr<J> m_c1;
       std::unique_ptr<J> m_c2;
    };
    C'est un peu redondant là non? Si c1 et c2 sont déjà en déclarés en &&, on ne devrait pas avoir besoin du std::move(), si?
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  6. #6
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    @Médinoc, aucune idée, je met les deux parce que je sais que ça marche comme ça. Mais oui, ça marche peut être sans le std::move.

  7. #7
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    @Médinoc, aucune idée, je met les deux parce que je sais que ça marche comme ça. Mais oui, ça marche peut être sans le std::move.
    Ça ne fonctionne pas sans, c1 et c2 sont vues comme des références, non comme une rvalue.
    Pour généraliser, toute variable est une référence, std::move est donc obligatoire.

  8. #8
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Citation Envoyé par jo_link_noir Voir le message
    Ça ne fonctionne pas sans, c1 et c2 sont vues comme des références, non comme une rvalue.
    Pour généraliser, toute variable est une référence, std::move est donc obligatoire.
    Je croyais que std::move() faisait juste un cast de T& en T&&?
    Et c1 et c2 sont déjà des std::unique_ptr<J>&&...
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  9. #9
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Je croyais que std::move() faisait juste un cast de T& en T&&?
    C'est bien le cas.

    Citation Envoyé par Médinoc Voir le message
    Et c1 et c2 sont déjà des std::unique_ptr<J>&&...
    C'est pas une vraie réponse, mais dès qu'on entend parler de move semantic, on parle du couple T&& / std::move.

    J'imagine que dès qu'on "utilise" un T&& il se "transforme" en autre chose (comme une ref); et qu'il faut un std::move pour forcer une rvalue.

  10. #10
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Si j'ai bien suivi les explications que j'ai lues, && est une marque pour permettre de prendre la référence, mais l'objet ainsi défini n'est qu'une référence très temporaire, et la transmettre à une autre && aurait pour effet de perdre la référence.

    C'est une partie de la problématique du perfect forward.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  11. #11
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Ça n'a absolument pas de sens, et pourtant c'est le cas: Je viens de tester sous Visual 2010, avec le move il transfère, sans le move il tente d'appeler le copy constructor sur le unique_ptr&& pour des raisons qui m'échappent totalement. Comment le && se transforme-t-il en const&? (et s'il tente de copier ça, pourquoi ne tente-t-il pas de copier le résultat du move, qui est un && aussi?)

    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
    struct A {
    	A(std::unique_ptr<J>&& c1, std::unique_ptr<J>&& c2):
    		m_c1(std::move(c1)),
    		m_c2(std::move(c2))
    		//m_c1(c1), //WTF??
    		//m_c2(c2)
    	{ }
     
    	std::unique_ptr<J> m_c1;
    	std::unique_ptr<J> m_c2;
    };
     
    void Test()
    {
    	std::unique_ptr<J> pj1(new double(4.2));
    	std::unique_ptr<J> pj2(new double(10));
    	A a(std::move(pj1), std::move(pj2)); //Ce move-là n'est pas un problème, je vois parfaitement à quoi il sert
    	std::cout << "m_c1=" << a.m_c1 << " - m_c2=" << a.m_c2 << std::endl;
    }
    Cela voudrait-il dire en gros, qu'un && qui n'est pas un temporaire non-nommé n'est pas un vrai &&?
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  12. #12
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    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
    template <class T>
    T&& foo(T&& a) {
    	return static_cast<T&&>(a);
    }
     
    template <class T>
    T&& bar(T& a) {
    	return static_cast<T&&>(a);
    }
     
    typedef std::unique_ptr<J> T;
     
    struct A {
    	A(T&& c1, T&& c2, T&& c3, T&& c4) :
    		m_c1( c1 ), // error
    		m_c2( static_cast<T&&>(c2) ), // ok
    		m_c3( foo(c3) ), // error
    		m_c4( bar(c4) ) // ok
    	{ }
     
    	std::unique_ptr<J> m_c1;
    	std::unique_ptr<J> m_c2;
    	std::unique_ptr<J> m_c3;
    	std::unique_ptr<J> m_c4;
    };
    T&& : erreur, on ne peut pas caster un 'T& &&' en 'T&& &&' implicitement (cast implicite 'T&' -> 'T&&' impossible, donc c'est le ctor par copy qui est appelé)
    caster un T&& en T&& : ok, j'imagine que je cast un 'T&' en 'T&&' ici, le paramètre est un 'T& &&', ce qui devient un 'T&'
    caster un T&& en T&& via une fonction prenant un T&& en paramètre : erreur, identique au cas 1 (on tente un cast implicite 'T&' -> 'T&&' lors de l'appel de foo)
    caster un T&& en T&& via une fonction prenant un T& en paramètre : ok, identique au cas 2

    Le C++, c'est simple et logique

  13. #13
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    Problème classique avec les lvalue/rvalue

    Et c1 et c2 sont déjà des std::unique_ptr<J>&&...
    Est ce que c1 et c2 sont des ravlue ou des lvalue ?
    C'est facile a répondre, il suffit de se poser la question : "est ce que je peux obtenir l’adresse de cette variable ?". La réponse est oui, donc ce sont des lvalues.
    La confusion vient du fait que ce sont des lvalue de type "rvalue reference". (Tout comme "T const& x", x est une lvalue sur une "lvalue reference").

    Il faut simplement retenir que pour avoir un move, il faut passer une rvalue ref de type rvalue ref (T&& &&), donc caster c1et c2 en ravlue ref (donc move ou cast). Tous les autres cas, on a une lvalue ref (T&& &, T& && et T& &).

    Du coup :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    m_c1( c1 ), // T&& & => T& => erreur, puisque unique_ptr non copiable
    m_c2( static_cast<T&&>(c2) ), // T&& && => T&&, ok, move du ptr
    m_c3( foo(c3) ) // incompatibilité entre T&& (foo) et T& (c3)
    m_c4( bar(c4) ) // compatibilité entre T& (bar) et T& (c4)
    A lire absolument : Effective Moderne C++ (5eme partie). Il y a aussi de très bons articles de blog.

  14. #14
    Membre averti Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Points : 341
    Points
    341
    Par défaut
    Re-salut à tou(te)s.

    J'ai commencé à changer l'implémentation des classes. J'utilise des unique_ptr pour "acheminer" un objet depuis le scope où il est créé (dans un builder prenant des options -pas forcément de façon très élégante mais passons) jusqu'à l'objet qui le contient par composition. Surprise : je galère.

    Je crois avoir compris le principe global du move (merci à cet article http://stackoverflow.com/questions/3...move-semantics). J'ai certainement pas pigé toutes les subitilités énoncées plus haut (mais ça vient en re-relisant l'article), et ça doit m'handicaper un chouïa pour comprendre ce qui se passe dans mon code, qui utilise pas mal de compositions.
    Le builder en question a comme membre un objet de type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
     std::vector<std::pair<std::shared_ptr<const A>, std::unique_ptr<B> > > m_AB
    Le but de ce membre est d'accumuler progressivement des données via l'interface du builder.
    Une fois qu'on a ajouté au builder suffisamment de données (A,B), on demande au builder de construire un objet C. L'objet C devrait choper le membre du builder pour se l'approprier.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    std::shared_ptr<const A> a;
    std::unique_ptr<B> > b;
    builder.AddAB(a, std::move(b)); //
    std::unique_ptr<C> pC= builder.Build();
    // ... et pC sera lui même utilisé dans une autre composition...
    J'aimerais être sûr des choix que j'ai fait, notamment parce que je trouve que ça commence à faire pas mal de pointeurs dans ce code :

    1) La donnée A est vouée à être relativement volumineuse, accessible par plusieurs objets, mais faut surtout pas la modifer : std::shared_ptr<const A> est approprié n'est-ce pas ?

    2) Par contre je suis moins certain de l'utilité du unique_ptr pour B : c'est un objet qui encapsule quelques autres objets, pas forcément très lourds, mais qui contiennent des vecteurs de pointeurs. Sa propriété est affectée à C, qui le contient. Dans ma tête, ça allait être galère à copier, donc je file directement passer l'adresse. C'est ok ou c'est lazy&ugly ?

    3) Et puis évidemment y'a ce type qui semble me provoquer des erreurs : std::vector<std::pair<std::shared_ptr<const A>, std::unique_ptr<B> > > : je crois que ça pose des problèmes avec la sémantique move ? J'ai arpenté plusieurs discussions, et j'ai pas forcément bien défini si c'était à éviter. Je me dis que l'objet de ce type est forcément copié à un moment pour passer du membre du builder au membre de l'objet buildé C : il manque peut être une fonction de copie à implémenter qui puisse utiliser le move. Il faudra bien un jour faire un move(pair.second) non ? A moins qu'il ne soit possible d'éviter la copie ? Je dis n'importe quoi ? C'est du coup l'occasion de demander si pour un type comme celui là qui n'a d'autres buts que d'associer les objets, il vaut mieux faire une petite classe ? Enfin voilà, comme d'habitude j'ai un problème et je ne sais pas dans quelle direction aller

    Merci d'avance de votre aide.
    Larguement vôtre,
    Le débutant, lui, ignore qu'il ignore à ce point, il est fier de ses premiers succès, bien plus qu'il n'est conscient de l'étendue de ce qu'il ne sait pas, dès qu'il progresse en revanche, dès que s'accroît ce qu'il sait, il commence à saisir tout ce qui manque encore à son savoir. Qui sait peu ignore aussi très peu. [Roger Pol-Droit]
    Github
    Mon tout premier projet: une bibliothèque de simulation de génétique des populations

  15. #15
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Salut,

    Déjà, de manière générale, il faudrait savoir ce que sont tes classe A, B et C... Car il n'y a pas de raison d'utiliser des pointeurs si elles ont sémantique de valeur :

    Typiquement, on n'utilisera l'allocation dynamique de la mémoire (enfin, make_shared depuis C++11 et make_unique depuis C++14) que pour les types qui interviennent dans une hiérarchie de classe. On dit alors d'eux qu'ils ont sémantique d'entité

    Ensuite, peut être t'y prend tu simplement mal Peut être devrais tu envisager (si c'est possible) d'avoir un AHolder quelque part qui maintient l'ensemble des données de type A et un BHolder ailleurs qui maintient l'ensemble des données de type B ailleurs, dans lequel le builder de C pourrait aller "piocher" toutes les données dont il a besoin pour construire la donnée C qui serait elle-même maintenue en mémoire dans un CHolder

    Cela te permettrait de faire en sorte que le véritable propriétaire de tes A, B et C soit à chaque fois le holder correspondant et d'utiliser non plus des pointeurs mais des références pour représenter les liaisons qui existent, du moins, si elles existent forcément. Au final, le seul cas où tu aurais besoin d'utiliser des pointeurs (qui pourraient être nus, si tu décide de jamais invoquer delete toi-même) serait celui où une relation peut ne pas exister. Et encore : si ce n'est pas pour mettre le pointeur dans une collection quelconque, car, dans ce cas, tu pourrais utiliser std::reference_wrapper

    En d'autre termes, si A, B et C ont bel et bien sémantique d'entité (ca, c'est la première chose à vérifier!!!), tu pourrais avoir quelque chose comme:
    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
     
    /* "quelque part", on a une fabrique de A qui ressemble à */
    class AFactory{
    public:
        std::unique_ptr<A> create(/*...*/);
    }
    class HolderA{
    public:
        /* pour le cas où l'on voudrait récupérer directement l'objet créé,
         * on peut décider d'en renvoyer une réérence
         */
        A& add(/* données nécessaires pour construire le A*/){
            auto temp = AFactory(/*param*/);
            /* Les instances de classes ayant sémantique d'entité sont
              * strictement unique et peuvent donc être identifiés très préciséments
             */
            auto recup = items_.emplace(std::make_pair(temp.id(),std::move(temp)));
            if(recup.second)
                return *(recup.first.second.temp.get());
            throw std::runtime_error("unable to add item");
        }
        A & find(size_t id){
           return *(items_.find(id).second.get());
       }
        A const & find(size_t id) const{
           return *(items_.find(id).second.get());
       }
       void remove(size_id){
           if(exists(id))
               items_.remove(id);
       }
       bool exists(size_t id ) const{
           return items_.find(id)!= items_.end();
       }
       void clear(){
            items_.clear()
       }
    private:
        std::map<size_t, std::unique_ptr<A>> items_;
    };
    /* si B doit référencer un a, la fabrique de B pourrait ressembler à  */
    class Bfactory{
    public:
        /* la fabrique de B n'a pas à s'inquiéter de rajouter des A si celui dont elle a besoin n'existe pas...
         * Elle ne devrait donc pas pouvoir accéder à add mais, qui sait... un B doit peut être pouvoir modifier un A???
         * le plus cool serait bien sur que B ne fasse qu'utiliser A sans jamais le modifier ;)  
         */
        BFactory(AHolder /*const */& ah):ah_(ah){
        }
        std::unique_ptr<B> create(/* données spécifiques à B, dont une id pour le A dont on a besoin*/){
            auto /*const*/ & found=ah_.find(id);
            /* le reste, selon les besoins */
       }
    private:
       AHolder /*const*/ & ah_;
    }
    class BHolder{
        /* meme chose que AHolder, mais pour des B.. */
    };
     
    /* Reste le problème des B qui pourraient ressembler à 
     */
    class B{
    public:
        B(A /*const*/ & a):a_(a){}
    private:
        A /* const */ & a_;
    };
    /* Enfin, le C pourrait ressembler à  */
    class C{
    public: 
        /* je serais surpris que C n'ait pas une sémantique de collection...
          * il y a peu de chances pour qu'on veuille le construire directement 
          * avec l'ensemble des éléments que contient son membre items_
          *
          * par contre, on voudra sans doute ajouter des éléments, en retirer,
         * vider la collection...
         */
    private:
        std::vector<std::pair<std::reference_wrapper<B>, std::reference_wrapper<A>>> items_;
    };
    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

  16. #16
    Membre averti Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Points : 341
    Points
    341
    Par défaut
    Salut,

    Avant tout merci de ta réponse

    En d'autre termes, si A, B et C ont bel et bien sémantique d'entité (ca, c'est la première chose à vérifier!!!)
    Yep, ça oui, c'est bien le cas. En fait ces sont tous des petits modèles mathématiques emboîtés : deux modèles peuvent être strictement identiques en leurs paramètres, mais si ils sont appliqués à différentes données à différents moments, que leurs états peuvent varier par la suite de manière indépendante, alors même si ils étaient à la base identiques c'est vraiment pas les même bonhommes, donc je crois pouvoir dire que oui c'est une sémantique d'entité

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
        /* je serais surpris que C n'ait pas une sémantique de collection...
          * il y a peu de chances pour qu'on veuille le construire directement 
          * avec l'ensemble des éléments que contient son membre items_
          *
          * par contre, on voudra sans doute ajouter des éléments, en retirer,
         * vider la collection...
         */
    Je suis pas forcément au point sur ces histoires de sémantique, mais je pense que dans ce cas, je n'ai pas envie d'avoir une collection. C'est pour ça que c'est le builder qui est chargé de cette sémantique (ajouter, retirer, vider...) histoire de tamponner les choix de l'utilisateur et proposer une interface sympatoche (pour l'instant de façon très minimaliste : il peut juste ajouter ).
    Une fois que les choix de construction ont été faits, C est construit, il est cohérent, bien identifiable, non-modifiable dans ses constituants, paré à se faire emboîter la tronche à son tour pour proposer les trois services principaux de sa classe (donner des infos sur son état, calculer des valeurs selon ses données, modifier son état de manière contrôlée par ses propres composants). Je ne sais pas ce que ça vaut en terme de design, mais j'ai l'impression qu'isoler les services de cette manière m'épargne des galères.

    Peut être devrais tu envisager (si c'est possible) d'avoir un AHolder quelque part qui maintient l'ensemble des données de type A et un BHolder ailleurs qui maintient l'ensemble des données de type B ailleurs, dans lequel le builder de C pourrait aller "piocher" toutes les données dont il a besoin pour construire la donnée C qui serait elle-même maintenue en mémoire dans un CHolder
    Tiens donc ça existe ? Content de voir que j'avais eu une idée pas totalement à côté de la plaque. J'avais un temps envisagé cette solution (en plus crade : maintenir en vie dans le Builder les objets référencés par les constructeurs des composants ), mais quand j'y avais réfléchis un chouïa ça compliquait inutilement le schmilblick, et y'avait quand même un problème de responsabilité : ma situation, c'est d'avoir pleins d'objet, qui ont tous sémantique d'entité (à part peut être pour ceux qui sont tout au fond du fond de la hiérarchie de composition - faut que j'y réfléchisse), et qui sont tous imbriqués les uns dans les autres. Lors de la destruction, aucun composant n'a de raison de survivre si son composite a été détruit. J'en suis pratiquement archi-convaincu (pour être tout à fait honnête, il pourrait y avoir une situation dans laquelle ce serait cool que les composantes survivent, mais je n'en suis pas certain et de toute façon je suis franchement à des années-lumières de cet état d'avancement du projet ).

    Cela te permettrait de faire en sorte que le véritable propriétaire de tes A, B et C soit à chaque fois le holder correspondant et d'utiliser non plus des pointeurs mais des références pour représenter les liaisons qui existent, du moins, si elles existent forcément. Au final, le seul cas où tu aurais besoin d'utiliser des pointeurs (qui pourraient être nus, si tu décide de jamais invoquer delete toi-même) serait celui où une relation peut ne pas exister. Et encore : si ce n'est pas pour mettre le pointeur dans une collection quelconque, car, dans ce cas, tu pourrais utiliser std::reference_wrapper
    Je n'ai pas compris ce passage , je vais chercher et je reviens.
    Le débutant, lui, ignore qu'il ignore à ce point, il est fier de ses premiers succès, bien plus qu'il n'est conscient de l'étendue de ce qu'il ne sait pas, dès qu'il progresse en revanche, dès que s'accroît ce qu'il sait, il commence à saisir tout ce qui manque encore à son savoir. Qui sait peu ignore aussi très peu. [Roger Pol-Droit]
    Github
    Mon tout premier projet: une bibliothèque de simulation de génétique des populations

  17. #17
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Avant toute chose, une petite précision : j'ai pris l'habitude de nommer très précisément les différents éléments que j'utilise dans mon code pour éviter toute ambiguité quant à l'utilité ou à l'usage qui peut en être fait. Je t'encourage d'ailleurs à prendre cette habitude le plus vite possible

    Dés lors, lorsque j'ai utilise les noms de AHolder de de BHolder, il suffit de connaitre un tout petit peu l'anglais pour se rendre compte que Holder se traduit littéralement par "possesseur","proriétaire","tittulaire" ou "détenteur". Ainsi, une classe nommée AHolder représente exactement ce que le nom indique "a class which holds items of type A" (une classe qui est propriétaires d'éléments de type A), tout simplement

    Ceci étant dit, je crois avoir (enfin ??? ) compris ce que tu cherches à faire... Je vais donc dérouler mon raisonnement sur un certain nombre de suppositions. Certaines d'entre-elles peuvent s'avérer fausses, et, comme certaines suppositions se basent sur des suppositions faites plus tôt, elle seront surement également fausses.

    N'hésites donc pas à me dire lorsqu'une supposition est fausse, car "tout ce qui suit" aura forcément perdu son intérêt

    Donc, si j'ai bien compris, tu envisage d'utiliser quelque chose ressemblant au patron de conception composite pour représenter des modèles mathématiques, ce qui semble indiquer que tu aurais des modèles "simples" et des modèles "composés". Ne sachant pas trop de quels modèles il est question, nous pourrions considérer que les quatre opérations de base (addition, soustraction, multiplication et division) sont des modèles simples, et que le calcul de la factorielle, de l'exponentielle ou même du modulo pourraient représenter des modèles "composés" parce que la factorielle prendrait une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    temp = 1
    pour i = 1 à X
        temp = multiplication(temp, i)
    fin pour
    renvoyer temp
    et que l'exponentielle pourrait prendre la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    temp = x
    pour i = 1 à exposant
        temp = multiplication(temp, x)
    fin pour
    renvoyer temp
    Et, bien sur, nous pourrions très bien baser d'autres modèles mathématiques sur tous les modèles envisagés : il y a tout plein de modèles qui peuvent utiliser la multiplication, et sans doute presque autant qui pourraient utiliser l'exponentielle ou la factorielle!

    La première chose dont il faut se rendre compte, c'est que, si un modèle "complexe" peut utiliser plusieurs modèles (qu'ils soient simples ou complexes n'a pas beaucoup d'importance), il se peut aussi qu'un modèle (qu'il soit simple ou complexe n'a pas beaucoup d'importance) peut être utilisé par plusieurs modèles "plus complexes". La relation 1/0...n que l'on voit sur le shéma proposé par David correspond ici à la relation utilise ou à une relation "est propriétaire de" (avec droit "de vie et de mort" sur l'objet possédé).

    Le problème, c'est que ce n'est pas parce que l'on détruit un objet "complexe" (par exemple, la factorielle), que l'on détruit forcément les modèles "plus simples" qui le composent (par exemple, la multiplication).

    Par contre, si on détruit un modèle "simple", alors, tout le château de cartes s'effondre : si on détruit le modèle "multiplication", il est impossible d'avoir le modèle "factorielle", et si on n'a plus la factorielle, alors tous les modèles qui utilisent la factorielle s'effondrent, et avec eux, tous les modèles qui utilisent les modèlent qui utilisent la factorielle et ainsi de suite jusqu'aux modèles les plus complexes.

    La deuxième chose à remarquer, c'est que, pour qu'un modèle "complexe" (devrais-je dire "plus complexe") puisse exister, il faut impérativement que les modèles "plus simples" qui le composent existent déjà: je serai totalement incapable de créer le modèle "factorielle" ou le modèle "exponentielle" si je ne dispose pas, en prérquis, du modèle multiplication (et, au passage, d'un modèle incrémentation utilisant l'addition pour le compteur )

    De plus, chaque modèle est a priori unique : qu'il soit utilisé dans le calcul de la factorielle ou dans celui de l'exponentielle, la multiplication reste la multiplication, et il n'y a absolument aucune raison d'avoir... deux instances du modèle multiplication. Note au passage que c'est l'une des caractéristiques essentielles des classes ayant sémantique d'entité

    Si ta situation correspond à tout ce que j'ai dit jusqu'à présent, continuons. Sinon, la suite n'aura pas grand intérêt (fait le moi savoir ).

    La seule conclusion à laquelle nous pouvons arriver est déjà que le patron de conception composite ne correspond pas vraiment à tes besoins, car il y a une relation de propriété entre un élément complexe et les éléments "plus simples" qu'il contient : si tu efface un dossier, tu efface aussi forcément les fichiers et les dossiers qu'il contient, alors que si tu détruit le modèle "exponentielle", tu ne détruit pas forcément le modèle "multiplication"...

    J'ai donc l'impression que tu te trouves d'avantage dans une situation d'utilisation de graphes : chaque modèle mathématique représente un point du graphe, et les modèles complexes définissent le chemin à suivre (en utilisant des modèles "plus simples) pour obtenir le résultat escompté.

    De plus, la relation utilise n'a réellement de l'intérêt... que pour le modèle complexe, pour lui permettre de savoir à quel modèle "plus simple" il doit faire appel. La relation qui pourra réellement décider de la vie ou de la mort d'un modèle, c'est la relation "est utilisé par" :
    • si le modèle A utilise B, il n'y a en effet aucune raison de détruire B lorsque A cesse d'exister, par contre
    • si le modèle B est utilisé par le modèle A, la destruction de B impliquera forcément la destruction de A.

    Sommes nous toujours d'accord Si oui, continuons.

    Oui, mais, si le modèle complexe n'est plus le "propriétaire légal" (avec droit de vie et de mort) du modèle "plus simple", la bonne question est de savoir... à qui peut bien échoir cette responsabilité. Et la réaction est toute trouvée : à une instance (unique, pour éviter d'en avoir qui "trainent dans tous les coins") d'une classe que nous nommerons pour la cause ... ModelHolder ("propriétaire","possesseur" de modèles mathématiques).

    Comme on se fout pas mal de savoir si nous "stockons" un modèle complexe ou non dans cette classe, et que, de plus, c'est la classe ModelHolder qui recoit le droit de vie et de mort sur les modèles, nous pourrons les stocker à l'intérieur de cette classe sous la forme d'une collection (il faudra déterminer laquelle) de... pointeurs intelligent sur la classe de base correspondant au modèle (sans autre distinction).

    De manière générale, notre ModelHolder ressemblera très fort à la classe AHolder de mon intervention précédente

    Nous devons maintenant réfléchir comment représenter la relation "utilise un modèle plus simple" dans les modèles complexes. Hé bien, si nous décidons de placer un pointeur vers un "modèle sans précision", nous allons être tentés d'invoquer delete à tors et à travers sur ce pointeur.
    Par contre, personne n'essayera jamais d'invoquer delete sur une référence!!! Et, le mieux de tout, c'est que, comme nous n'avons aucune idée du nombre de modèles simples que notre modèle complexe va utiliser, nous avons largement intérêt à placer les modèles "plus simple" que notre modèle complexe va utiliser dans une collection dynamique (un tableau ou une liste, par exemple).

    Et, si je dit "le mieux de tout", c'est parce que C++11 est arrivé avec une classe géniale : la classe std::reference_wrapper. On ne peut en effet pas créer de collection (quelle qu'elle soit) de référence, vu qu'une référence n'est qu'un "alias" sur un objet existant par ailleurs. Mais la classe std::reference_wrapper est une classe et, comme pour toute les classes, les instances de cette classe sont des objets qu'il est tout à fait possible de placer dans une collection (quelle qu'elle soit).
    Ainsi, nous pouvons partir d'une hiérarchie de classe proche de
    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
    /* la classe de base */
    class Model{
    public:
        public Model(std::string const & name):name_(name), id_(std::hash<std::string>()(name)){}
        /* changeons un peu : voici le moyen d'identifier chaque modèle de manière unique ...
         */
        std::string const &name() const{
            return name_;
       }
       /* un autre moyen, plus efficace pour l'idenifier */
       size_t id() const {
           return id_;
        }
        virtual void execute() = 0;
    private:
        std::string name_;
        size_t id_;
    };
    /* un modèle "simple" (addition, soustraction, ... */
    class SimpleModel : public Model{
    public:
        SimpleModel(std::string const & name):Model(name){}
        void execute() override;
    };
    /* un modèle complexe (factorielle, ... ) */
    class ComplexModel : public Model{
    public:
        ComplexModel(std::string const & name):Model(name){}
        void execute() override;
    private:
       std::vector<std::reference_wrapper<Model>> subModels_; // ou toute autre collection au choix
    };
    /* et le fameux holder */
    class ModelHolder{
    public:
        /* à peu près tout ce que j'ai mis dans mon intervention précédante */
    private:
        /* boost::bimap nous permettrait de choisir aussi bien par id que par nom...
         * ca pourrait faciliter énormément les choses ;)
         */
        std::map<std::string, std::unique_ptr<Model>> allModels_;
    };
    Il ne nous reste maintenant plus qu'une seule chose à faire : nous assurer que la destruction d'un modèle utiliser par d'autre(s) modèle(s) provoquera systématiquement la destruction de tous les modèles qui les utilisent (et de tous les modèles qui utilisent les modèles qui utilisent et ainsi de suite le modèle détruit).

    Nous avons vu que la relation qui peut mener à cette décision est la relation "(le modèle A) est utilisé par (le modèle B)" et qu'elle doit provoquer la destruction de l'objet B. Seulement, s'il est "cohérent" qu'un modèle complexe puisse accéder aux modèles "plus simples" qu'il utilise, il n'y a aucune raison pour qu'un modèle "simple" puisse ne serait-ce que avoir connaissance de tous les modèles qui l'utilisent!!!

    L'idée est donc de créer un type de donnée qui pourrait modéliser la relation "utilisateur / utilisé" sous une forme qui pourrait être aussi simple que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    struct UsageRelation{
        size_t userId; // l'identifiant unique de l'utilisateur
        size_t usedId; // l'identifiant unique de l'utilisé
    };
    et de placer toutes ces relations dans une classe ( RelationHolder )qui travaillera en relation avec notre classe ModelHolder, l'idée générale étant que notre classe ModelHolder indiquera systématiquement l'identifiant de tous les élément qu'elle s'apprête à supprimer à la classe RelationHolder et que la classe Relation Holder indiquera en retour à la classe ModelHolder tous les modèles qui doivent être supprimés à cause du premier retrait.

    Mais ca, je vais peut être te laisser réfléchir à la manière de t'organiser pour y arriver

    Pour conclure, les questions qui tuent :

    Mon analyse de la situation correspond-elle bien à la situation dans laquelle tu te débat désespérément
    Que pense tu de la cohérence de ce que je te propose :question
    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

  18. #18
    Membre averti Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Points : 341
    Points
    341
    Par défaut
    Pfiouuu mais où trouvez-vous le temps de répondre aussi précisément ? Merci, vraiment.
    Donc, si j'ai bien compris, tu envisage d'utiliser quelque chose ressemblant au patron de conception composite pour représenter des modèles mathématiques, ce qui semble indiquer que tu aurais des modèles "simples" et des modèles "composés".
    Chef oui chef ! Mais pas aussi simples que des opérateurs : ces modèles possèdent un état (valeurs de paramètres) et sont identifiables par la place qu'ils occupent dans les modèles plus compliqués (cf plus bas).

    De plus, chaque modèle est a priori unique : qu'il soit utilisé dans le calcul de la factorielle ou dans celui de l'exponentielle, la multiplication reste la multiplication, et il n'y a absolument aucune raison d'avoir... deux instances du modèle multiplication. Note au passage que c'est l'une des caractéristiques essentielles des classes ayant sémantique d'entité
    Mon analyse de la situation correspond-elle bien à la situation dans laquelle tu te débat désespérément ?
    Je ne me débat pas desespérement, je suis dans un " subtil mélange entre lâcher prise et tenir bon" comme dirait l'autre. Plus sérieusement je crois que cet exemple descend beaucoup plus "bas" que ce dont j'ai besoin. Mais du coup je comprends beaucoup mieux tout ton discours, et je comprends que j'ai omis de mentionner une information assez centrale : tout mon programme tourne autours de la notion de modèle paramétré. Le mieux serait d'expliquer carrément ce que je fais comme ça on sera clair
    Je fais une thèse en écologie et génétique, qui a débouché sur pas mal d'info (le C++ intensif, c'était pas prévu à la base ).

    Ce que je cherche à faire, c'est prévoir des dynamiques écologiques (genre des bestioles qui se reproduisent et de déplacent en fonction de l'environnement).
    Du coup je construis un modèle qui rende compte de la croissance des bestioles, de leur déplacement dans l'espace, et surtout du processus qui aboutit aux données dont on dispose (effort d'échantillonnage, modèles génétiques...).
    Ce qu'on aimerait, c'est trouver les valeurs des paramètres de ce modèle qui font que les données qu'on observe sont "très probables" (du coup, on pourrait utiliser ces valeurs pour faire des extrapolations temporelles). Mathématiquement ce genre de modèle c'est ingérable, donc l'idée à la mode c'est de bourriner sur les simulations de ce modèle pour trouver quels paramètres de simulations aboutissent à des données simulées"suffisamment proches" de ce qu'on observe.

    Reste donc à bien identifier ce que j'entends par "modèles paramétré". J'ai au cours de mes pérégrinations objets défini plusieurs "gros" concepts :
    - un modèle de croissance démographique : lui il doit définir la relation entre l'environnement et les capacités de croissances des pop. Il peut simuler la croissance de la population.
    - un modèle de déplacement : il représente les probabilités de déplacement en fonction de distances ("distances" c'est volontairement flou : distances géographiques, environnementales...) (N.B. :Ces deux modèles sont assez gros car ils embarquent toutes la structure de dépendance environnementales : les données, les modèles intérmédiaires s'y appliquant)
    Et des modules de génération de données qui interrogent le résultats de la simulation démographiques pour fabriquer des données :
    - un modèle de données d'observation (plus y'a de bestioles et plus y'a d'humains, plus les proba de rencontre/observation sont élevées)
    - un modèle de données génétiques (en gros les patrons génétiques dépendent vachement de la démographie).


    Et tout ces modules, il faut pouvoir :
    1- lancer une simulation : croissance + déplacement = démographie -> on envoie ça dans les modules de génération de données
    2- récupérer les données et les valeurs de paramètres, envoyer tout ça dans des algo très compliqués qui détermineront si oui ou non c'est assez proche de ce qu'on observe vraiment.
    3- modifier les valeurs des paramètres selon des lois de proba bien définies pour explorer de nouvelles combinaisons de paramètres. Puis recommencer le cycle.
    A vrai dire ces trois points sont tellement centraux que pas mal de classes se retrouvent à hériter de l'interface regroupant ces trois services.

    Du coup ce sera peut être plus clair :
    Je peux avoir un modèle (par exemple ax +b pour faire simple) représentant la probabilité de mort des bestioles en fonction de la température, et le "même" pour représenter la probabilité de naissance en fonction de la pluie, et pourtant ils n'auront RIEN à voir : ils n'analyseront pas les même données, et ne visiteront pas les même valeurs de paramètres en même temps.

    Du coup j'ai fait des classes de foncteurs : Linear, Gaussian, FatTail... qui représentent les fonctions de base (c'est déjà des grosses fonctions, pas des opérateurs mathématiques). On peut
    1- les appliquer à une donnée ( Linear(2.4) = 45.6 ), ce qui permet in fine la simulation numérique
    2- leur demander leurs valeurs de paramètres (par ce que c'est ça l'info centrale quand même)
    3- modifier leurs paramètres

    Et c'est ce dernier point qui m'a posé des soucis (et que plusieurs d'entre vous m'ont aidé à résoudre), car la modification des paramètres se fait en tirant chaque nouvelle valeur dans une distribution (qui est également paramétrée) qui a été précisée pour chaque paramètre en début de programme. J'ai un peu galéré, mais la hiérarchie de classe semble tenir le coup.

    A chaque étape de l'algorithme chaque instance de "modèle" a une vie propre (il s'applique à telle donnée, il envoie son résultat à tel objet, il bouge dans son espace des paramètres de telle manière). J'ai l'impression que ça règle la question du singleton (c'est là que tu voulais venir ? ) non ?
    Je me dis également qu'il n'a aucune raison d'exister sorti de sa structure (aucun raison d'obtenir des proba de mortalité si c'est pas pour simuler de la démographie). Mais je manque d'expérience pour être tout à fait sûr de ce point. Notamment tu dis :
    La relation qui pourra réellement décider de la vie ou de la mort d'un modèle, c'est la relation "est utilisé par" :
    si le modèle A utilise B, il n'y a en effet aucune raison de détruire B lorsque A cesse d'exister, par contre
    si le modèle B est utilisé par le modèle A, la destruction de B impliquera forcément la destruction de A.
    Je ne dois pas avoir l'esprit très clair sur ce point, car j'ai l'impression que A utilise B, mais qu'en plus B ne sert à rien si il n'est pas utilisé par A. Donc j'ai envie de charger A de se débarasser de B dès qu'il n'en a plus besoin, parce que de toute façon personne n'en voudra (c'est horrible quand on y pense ). Je me trompe quelque part ? )

    Par contre, si on détruit un modèle "simple", alors, tout le château de cartes s'effondre : si on détruit le modèle "multiplication", il est impossible d'avoir le modèle "factorielle", et si on n'a plus la factorielle, alors tous les modèles qui utilisent la factorielle s'effondrent, et avec eux, tous les modèles qui utilisent les modèlent qui utilisent la factorielle et ainsi de suite jusqu'aux modèles les plus complexes.
    Tout à fait d'accord avec toi... si il n'y a qu'une seule instance de chaque modèle. Dans mon programme y'en a plusieurs car chaque instance est identifiable par les valeurs de paramètres qu'elle possède et par la place qu'elle occupe dans l'architecture générale du modèle (et de ce point précis on peut discuter ) J'ai donc voulu m'assurer que les modèles "simples" ne seront détruits que quand les modèles complexes seront détruits, et j'ai encore un chouïa de travail là-dessus.

    Et, si je dit "le mieux de tout", c'est parce que C++11 est arrivé avec une classe géniale : la classe std::reference_wrapper. On ne peut en effet pas créer de collection (quelle qu'elle soit) de référence, vu qu'une référence n'est qu'un "alias" sur un objet existant par ailleurs. Mais la classe std::reference_wrapper est une classe et, comme pour toute les classes, les instances de cette classe sont des objets qu'il est tout à fait possible de placer dans une collection (quelle qu'elle soit).
    Wooh, brutal ! J'adopte !

    Encore merci pour ta réponse. A vrai dire, la situation actuelle dans laquelle je suis est assez différente à cause de ces fichus paramètres. Mais tout ce que tu développe me sera bien utile plus tard car ça me fait faire des liens avec une fonctionnalité encore non abordée du programme (déjà là j'essaie d'avoir un truc basique qui marche... )
    Le débutant, lui, ignore qu'il ignore à ce point, il est fier de ses premiers succès, bien plus qu'il n'est conscient de l'étendue de ce qu'il ne sait pas, dès qu'il progresse en revanche, dès que s'accroît ce qu'il sait, il commence à saisir tout ce qui manque encore à son savoir. Qui sait peu ignore aussi très peu. [Roger Pol-Droit]
    Github
    Mon tout premier projet: une bibliothèque de simulation de génétique des populations

  19. #19
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Seabirds Voir le message
    Pfiouuu mais où trouvez-vous le temps de répondre aussi précisément ? Merci, vraiment.
    Disons qu'on le prend

    Chef oui chef ! Mais pas aussi simples que des opérateurs : ces modèles possèdent un état (valeurs de paramètres) et sont identifiables par la place qu'ils occupent dans les modèles plus compliqués (cf plus bas).
    Oui, je me doutais que ce n'était pas "aussi simple" que les opérations mathématiques...

    Disons que j'essayais de donner un exemple "qui tienne la route" et qui se base sur quelque chose que "tout le monde connait"
    Je ne me débat pas desespérement, je suis dans un " subtil mélange entre lâcher prise et tenir bon" comme dirait l'autre. Plus sérieusement je crois que cet exemple descend beaucoup plus "bas" que ce dont j'ai besoin. Mais du coup je comprends beaucoup mieux tout ton discours, et je comprends que j'ai omis de mentionner une information assez centrale : tout mon programme tourne autours de la notion de modèle paramétré. Le mieux serait d'expliquer carrément ce que je fais comme ça on sera clair
    Je fais une thèse en écologie et génétique, qui a débouché sur pas mal d'info (le C++ intensif, c'était pas prévu à la base ).
    Ne t'en fais pas, j'ai cultivé depuis longtemps l'art subtil des phrases chocs. Je me suis rendu compte qu'en en pimentant mes textes, le message passait globalement mieux et était mieux retenu

    Ce que je cherche à faire, c'est prévoir des dynamiques écologiques (genre des bestioles qui se reproduisent et de déplacent en fonction de l'environnement).
    Du coup je construis un modèle qui rende compte de la croissance des bestioles, de leur déplacement dans l'espace, et surtout du processus qui aboutit aux données dont on dispose (effort d'échantillonnage, modèles génétiques...).
    Reste donc à bien identifier ce que j'entends par "modèles paramétré". J'ai au cours de mes pérégrinations objets défini plusieurs "gros" concepts :
    - un modèle de croissance démographique : lui il doit définir la relation entre l'environnement et les capacités de croissances des pop. Il peut simuler la croissance de la population.
    - un modèle de déplacement : il représente les probabilités de déplacement en fonction de distances ("distances" c'est volontairement flou : distances géographiques, environnementales...) (N.B. :Ces deux modèles sont assez gros car ils embarquent toutes la structure de dépendance environnementales : les données, les modèles intérmédiaires s'y appliquant)
    Et des modules de génération de données qui interrogent le résultats de la simulation démographiques pour fabriquer des données :
    - un modèle de données d'observation (plus y'a de bestioles et plus y'a d'humains, plus les proba de rencontre/observation sont élevées)
    - un modèle de données génétiques (en gros les patrons génétiques dépendent vachement de la démographie).
    Mais, de manière générale, tu utiliseras toujours les mêmes paramètres pour le même modèle... Par exemple, pour le modèle d'observation, ce sera le nombre de bestioles, le nombre d'habitants et la capacité de la bestiole à passer inaperçue. Ne dit on pas qu'il y a je ne sais plus combien plus de rats que d'humain pourtant, les rats sont tellement habiles à passer inaperçus que la probabilité d'en croiser un -- meme dans une grande ville -- reste relativement faible

    Toujours est-il que le modèle d'observation restera toujours le même, avec les mêmes paramètres et le même algorithme. C'est d'ailleurs aussi le cas pour les opérations mathématique : une division a besoin du dividende et du diviseur pour pouvoir fonctionner, mais le mode de fonctionnement reste toujours le même. Il faut donc "simplement" rajouter une notion (que je n'avais pas citée dans mon intervention précédente) importante : la notion de simulation qui fournit les valeurs de départ utilisées par les différents modèles

    <snip>Je ne dois pas avoir l'esprit très clair sur ce point, car j'ai l'impression que A utilise B, mais qu'en plus B ne sert à rien si il n'est pas utilisé par A. Donc j'ai envie de charger A de se débarasser de B dès qu'il n'en a plus besoin, parce que de toute façon personne n'en voudra (c'est horrible quand on y pense ). Je me trompe quelque part ? )
    Ben, en fait, tu crées différents modèles (avec les paramètres qui les intéressent pour une simulation donnée, en veillant à ce que tous les modèles créés soient utilisés (il ne sert à rien de créer un modèle d'évolution du temps au Sahara si tu fais une simulation sur la migration baleines à bosse dans l'antarctique ), mais ca s'arrête là
    Tout à fait d'accord avec toi... si il n'y a qu'une seule instance de chaque modèle. Dans mon programme y'en a plusieurs car chaque instance est identifiable par les valeurs de paramètres qu'elle possède et par la place qu'elle occupe dans l'architecture générale du modèle (et de ce point précis on peut discuter ) J'ai donc voulu m'assurer que les modèles "simples" ne seront détruits que quand les modèles complexes seront détruits, et j'ai encore un chouïa de travail là-dessus.
    Je vais donc préciser : unique au niveau de la simulation... Et encore... Du coup, ce n'est pas un modèle qui doit être responsable de la destruction des modèles qu'il utilise, mais la simulation qui doit être responsable de tous les modèles utilisés dans le cadre de la simulation effectuée: aucun modèle ne peut être détruit tant que la simulation n'est pas terminée, et "basta"

    Ou, plus précisément : on crées les modèles (du plus simple au plus complexe) au niveau de la simulation, et il n'y a que la simulation qui s'occupe de détruire tous les modèles une fois qu'elle a terminé son job
    Encore merci pour ta réponse. A vrai dire, la situation actuelle dans laquelle je suis est assez différente à cause de ces fichus paramètres. Mais tout ce que tu développe me sera bien utile plus tard car ça me fait faire des liens avec une fonctionnalité encore non abordée du programme (déjà là j'essaie d'avoir un truc basique qui marche... )
    A vrai dire, elle est beaucoup moins différente que ce que tu ne semble le croire...

    Car tout modèle mathématique, quel qu'il soit (y compris pour les opérations de base) a besoin de paramètres... La seule différence, c'est qu'une addition ou une multiplication se contente de deux paramètres alors que tu vas sans doute te débattre avec un nombre très largement supérieur de paramètres .

    Mas, au delà de ca, un modèle mathématique reste un modèle mathématique, qu'il soit aussi simple qu'une addition ou aussi compliqué que ceux que tu dois mettre en place
    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

  20. #20
    Membre averti Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Points : 341
    Points
    341
    Par défaut
    Mmhhhh je crois que je commence à comprendre !

    Je garde tout ça au chaud pour la prochaine étape de refactoring, ça permettra aux idées de décanter un peu dans mon cerveau

    Merci encore !
    Le débutant, lui, ignore qu'il ignore à ce point, il est fier de ses premiers succès, bien plus qu'il n'est conscient de l'étendue de ce qu'il ne sait pas, dès qu'il progresse en revanche, dès que s'accroît ce qu'il sait, il commence à saisir tout ce qui manque encore à son savoir. Qui sait peu ignore aussi très peu. [Roger Pol-Droit]
    Github
    Mon tout premier projet: une bibliothèque de simulation de génétique des populations

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Réponses: 1
    Dernier message: 21/12/2014, 20h03
  2. Passage par référence/pointeur
    Par NiamorH dans le forum C++
    Réponses: 5
    Dernier message: 15/07/2008, 10h05
  3. [pointeurs/référence]
    Par poukill dans le forum C++
    Réponses: 18
    Dernier message: 10/05/2006, 11h49
  4. Références et pointeurs sur un tableau
    Par smag dans le forum C++
    Réponses: 2
    Dernier message: 01/03/2005, 20h29
  5. Réponses: 8
    Dernier message: 26/08/2004, 18h59

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