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 :

Observateur (Pattern) - Destructeur


Sujet :

C++

  1. #1
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Par défaut Observateur (Pattern) - Destructeur
    Bonjour à tous,

    J'implémente un pattern très près de celui nommé "Observateur/Observable".

    Voici les règles :

    1. L'observateur démarre un thread et poll l'observable.
    2. Si l'observable est disponible, alors il s'y attache via la fonction Attach(). Sinon, il ré-essaye quelques secondes plus tard. Vous aurez donc compris que l'obersavateur nécessite un pointeur vers l'observable.
    3. L'observateur peut se détacher de l'observable quand il le désire, via une fonction Detach() que l'observable met à sa disposition.
    4. Lorsque l'observateur est détruit (destructeur), il doit impérativement se détacher de l'oberservable, si il y était attaché.
    5. Lorsque l'observable est détruit (destructeur), il doit impérativement détacher tous ses observateurs.


    Dison qu'un Observateur doit toujours dériver de CObservateurBase.
    Dison qu'un Observable doit toujours dériver de CObservableBase.

    Voilà où j'ai un petit problème. Le détachement !

    Lorsqu'un observateur est détruit, alors il est simple de le forcer à se détacher via son destructeur dans CObservateurBase.

    Par contre, lorsqu'un Observable est détruit, on peut aussi détacher tous ses observateur dans son destructeur dans CObservableBase. Mais, que se passe-t-il avec les observateurs qui le poll encore, leur pointeurs de type CObservableBase n'est plus valide !

    Alors, que devrais-je faire ?

    1. Dériver le CObservableBase de enable_shared_from_this et de lors de l'attachement d'un observateur, on lui remet un shared_ptr ? Ce qui prolongera la vie de l'observable aussi longtemps qu'un observateur y sera connecté ?
    2. Emmetre une exception/assert dans le destructeur de CObservableBase lorsqu'il reste des Observateur encore connecté ? J'avoue ne pas trop aimer déclancher une exception dans un destructeur de toute façon, mais au moins cela indique au programmeur qu'il a fait une bourde !
    3. Présumer que l'observable aura toujours une vie plus longue que ses observateur ?


    Merci

  2. #2
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    J'ai l'impression que ton message n'est pas terminé, mais je suggèrerais quand même de regarder du côté de weak_ptr. J'ai l'impression que c'est le genre de chose que tu recherches.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  3. #3
    Membre éclairé
    Avatar de Zenol
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2004
    Messages
    812
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2004
    Messages : 812
    Par défaut
    Un assert est différent d'une exception. Un assert indique un bug de programmation, là ou une exception informe d'une erreur dont on peux récupérer.

    À toi de décider si tu préfère supposer (et si c'est pertinent de le faire) qu'un observable vivras toujours plus longtemps que ses observateurs.

    Si tu souhaite forcer les observateurs à arrêter d'observer quand un observable meurt (ce qui est plutôt pertinent), tu peux t'en sortir avec des mutex pour les histoires de validité de ton pointeur.

    L'idée est que chaque observateur a une mutex, que tu lock quand tu utilise le pointeur de ton observable. En temps normal, seul l'observateur lock cette mutex et il n'y a pas de vrai surcout. Par contre, quand on observable meurt et veux forcer l'observateur à ne plus le regarder, il lock aussi cette mutex. De cette façon, tu es certain que le pointeur ne sera pas invalidé au "mauvais moment". (L'observateur est prévenu de la mort de l'observable avant que l'observable ne soit effectivement détruit).

    Si un observateur observe plusieurs observables, soit tu suppose que la mort prématuré d'une observable est vraiment rare, et tu reste sur une mutex par observateur, soit c'est vraiment fréquent et il te faut une mutex par paire (observateur, observable) (variable membre de l'observateur).

    Si tu peux faire du C++11, regarde std::thread, std::lock_guard et std::lock pour un lock de plusieurs mutex si nécessaire. Attention aux dead locks
    Mes articles Développez | Dernier article : Raytracer en haskell
    Network library : SedNL | Zenol's Blog : http://zenol.fr

    N'oubliez pas de consulter la FAQ et les cours et tutoriels.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 634
    Par défaut
    Salut,

    Avant de te répondre, je voudrais te poser deux questions qui te mettra sans doute sur la voie de la réponse:
    • qui prend la responsabilité de la durée de vie de tes observateurs
    • qui prend la responsabilité de la durée de vie de tes observable
    Une fois que tu auras la réponse à ces deux questions, médite sur la phrase de David Wheller
    Citation Envoyé par David Wheller
    all problems in computer science can be solved by another level of indirection
    (Tout problème en informatique peut être résolu par un autre niveau d'indirection)
    Car, de toutes évidences, tu as deux relations 1à n entre tes observateurs et tes observables :
    • 1 observateur peut observer plusieurs objets observables et
    • 1 objet observables peut être observé par plusieurs observateurs
    La solution passe peut-être par le fait de faire se connecter / se déconnecter les observateurs et les observables par "autre chose"
    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

  5. #5
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Par défaut
    @JolyLoic : Merci JolyLoic, c'est vrai mon message ne semblait pas terminé, mais il l'était. Erreur de copie/coller

    Pourriez-vous m'éclairer un peu plus avec l'usage du weak_ptr pour mon cas ?

    Merci

    @koala01 En fait c'est pas tout à fait vrai.

    1. Un observateur peut observer qu'un seul observable à la fois.
    2. Un observable peut être observé par un ou plusieurs observateur.


    Quelque observateur doivent être démarré à l'intérieur de la classe de l'observable. La vie de ceux-ci ne pose donc aucun problème.

    Un observateur peut aussi être démarré de l'extérieur d'un observable. Ceux-ci peuvent donc poser problème.

    En fait, c'est pas réellement un pattern d'observable. Dans mon cas, un observateur est bien un observateur, mais aussi un genre de writter/creator de données hébergé dans l'observable.

    @Zenol : Merci pour votre réponse

    Au sujet des ASSERTS VS Exceptions, je connais déjà la différence

    J'avais exploré la voie du mutex (section critique). J'imagine que les seul endroit ou l'on s'en sert c'est lorsque l'observateur utilise le shared_ptr de l'observable, et dans son destructeur. Et il sera aussi locké dans le destructeur de l'observable. Oufff, en espérant que cela ne provoque pas de deadlock. Qui plus est, OUI la performance est très importante, donc le lock doit être très efficace et de courte durée.

  6. #6
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    Si ton Observable est géré par un shared_ptr, et que ton Observator possède un weak_ptr<Observable>, alors :

    - Au moment de s'activer, l'Observator va créer un shared_ptr<Observable> à partir du weak_ptr<Observable>pour accéder à l'Observable.
    - Si la création échoue, c'est que l'Observable est mort, l'Observateur n'a plus qu'à e suicider proprement
    - Si la création réussi, tant que le shared_ptr<Observable> reste vivant (le temps du traitement), il empêche l'Observable de mourir.
    - Puis, le traitement fini, tu relâche le shared_ptr.

    C'est un peu comme ta solution à base de shared_ptr, mais la durée de vie n'est pas prolongée tant qu'un observator est connecté, uniquement tant qu'un Observator est en train d'utilisé l'Observable (afin de ne pas lui couper l'herbe sous le pied, mais quand même de permettre la libération).

    Est-ce plus clair ?
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  7. #7
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Par défaut
    JolyLoic :

    Si je comprend bien :

    1. Au lieu d'obtenir un shared_ptr via un weak_ptr de l'observable, est-mieux de dériver l'observable de enable_shared_from_this ?
    2. L'observable doit publier une fonction qui permet d'obtenir un weak_ptr depuis lui même.
    3. Dans la fonction de polling de l'observateur, on doit faire appel à la fonction de l'bservable qui permet d'obtenir le weak_ptr.


    Si j'ai bien compris, lorsque nécessaire, l'observateur (thread différent évidemment) construira un shared_ptr depuis le weak_ptr de l'observable. Prolongant donc sa vie le temps que l'observateur termine son travail.

    Donc, il est très important que l'observable soit totalement fonctionnel tant et aussi longtemps que son destructeur n'est pas appelé. Je veux dire parl-la ; Imaginons que ce denrier peut tomber dans plusieurs états différents (Prêt, en dormance, en erreur, etc..) et que le travail de l'observateur ne peut se faire que l'orsque l'observabl est en statut PRÊT. Il ne faudrait pas que l'observable démarre un traitement et qu'en même temps l'observable change d'état. Bref, ... tout un défi

    Dernière question, n'utilisant pas très souvent les SmartPointer en C++, (je suis plus old school disons), pour la création du shared_ptr initial de l'instance du Observable, devrais-je utiliser le factory pattern ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    class CObservable 
    { 
    public:
      static shared_ptr<CObservable *> CreateObservable();
    private:
      CObservable();
    protected:
      virtual ~CObservable();
    Merci encore pour votre patience, c'est apprécié.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 634
    Par défaut
    En fait, quitte à prendre le contre pied de Loïc, et parce que je n'apprécie que médiocrement les shared et les weak_ptr (par contre, j'aime énormément les unique_ptr ):

    Tu ne peux décemment pas demander à l'observateur de détruire les objets observables qu'il observe, pas plus que tu ne peux demander aux objets observés de détruire les observateurs qui les observent.

    Je vois essentiellement deux raisons à cela:

    La première est un simple problème de mise à jour: Si chaque observateur se charge de détruire les objets qu'il observe, il faut que les objets observés préviennent les autres observateurs qui les observent qu'ils sont détruits, et inversement.

    En outre, ce n'est pas parce qu'un objet n'est -- temporairement -- observé par aucun observateur qu'il doit forcément être détruit, ni parce qu'un observateur n'observe -- temporairement -- rien qu'il doit forcément être détruit.

    La deuxième raison est beaucoup plus conceptuelle et tient en trois lettres : SRP pour Single Responsability Principle (principe de la responsabilité unique, si tu préfères )

    La responsabilité d'un observateur est... de réagir lorsqu'un des objets qu'il observe lui signale qu'il a été modifié, et ces des objets observables est ... de signifier aux observateurs qui les observent qu'ils ont été modifiés.

    Pour l'un comme pour l'autre, cette seule responsabilité est déjà amplement suffisante !!! On ne peut pas, en plus, leur demander de gérer la durée de vie de l'autre !

    Cela signifie qu'il faudrait une troisième et une quatrième classe dans l'histoire : l'une qui se chargerait de maintenir la liste de tous les observateurs, et de les détruire "en temps utiles" et l'autre qui maintiendrait la liste de tous les objets observés, et de les détruire quand il le faut.

    Ces deux classes pourraient parfaitement maintenir des unique_ptr (soit sur les observateurs, soit sur les objets observables), et, quant à eux, tant les observateurs que les objets observables pourraient parfaitement contenir en interne une collections de pointeurs sur l'autre type, dont ils savent ne pas être propriétaires:

    Les observateurs manipulent les objets qu'ils observent sans jamais essayer de les détruire et, de leur coté, les objets observables manipulent les observateurs qui les observent, sans jamais tenter de les détruire.

    Pour le reste, on ne change rien à ton idée de départ : les observateurs s'enregistrent auprès des objets observables et maintiennent d'autre part la liste des objets auprès desquels ils sont enregistrés.

    Mais, la grosse différence, c'est que ce n'est plus l'observateur qui se "désinscrit" auprès des objets qu'il observe, pas plus que ce n'est l'objet observé qui se désinscrit auprès des observateurs qui l'observent:

    En effet, la classe qui maintient la liste de tous les observateurs (respectivement de tous les objets observables) et qui décide de les détruire lorsqu'ils sont devenus inutiles est idéalement placée pour s'occuper de la désinscription. l'amitié venant sans doute bien à point pour l'y aider.

    Ainsi, tu pourrais parfaitement arriver à quelque chose 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
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    class Observator{
        friend class ObservatorManager; // je n'aime pas le terme manager, 
                                        // mais je n'en ai pas d'autre en tete :P
        public:
           virtual ~Observator();
            /* ... */
            void registerItem(Observable *);
            void unregisterItem(Observable *);
            virtual void react(Observable const &);
        private:
            std::vector<Observable *> allObserved_;
    };
    class Observable {
        friend class ObservableManager; // je n'aime pas le terme manager, 
                                        // mais je n'en ai pas d'autre en tete :P
        public:
           virtual ~Observable ();
            /* ... */
            void registerItem(Observable *);
            void unregisterItem(Observable *);
        private:
            void emit() const; // appelle react() pour chaque observateur de la liste
            }
            std::vector<Observable *> allObservers_;
    };
     
    class ObservatorManager{
     
        public:
           void destroy(Observator *){
               /* Parcoure allObserved_ pour désinscrire l'observateur
                * auprès de tous les objets qu'il observait avant de
                * supprimer l'élément du tableau
                *
                * on profite du std::unique_ptr pour faire le ménage 
                * correctement ;)
                */
          }
            std::vector<std::unique_ptr<Observator>> allObservators_;
    };
     
    class ObservableManager{
     
        public:
           void destroy(Observable *){
               /* Parcoure allObserved_ pour désinscrire l'objet observé
                * auprès de tous les observateurs qui l'observait avant de
                * supprimer l'élément du tableau
                *
                * on profite du std::unique_ptr pour faire le ménage 
                * correctement ;)
                */
          }
            std::vector<std::unique_ptr<Observable >> allObservavbles_;
    };
    (je laisse de grands blancs, à toi de les remplir )

    L'avantage à travailler de la sorte, c'est que tu "centralise" le point où l'un ou l'autre est détruit, en t'assurant qu'il sera correctement désinscrit de tous les objets qu'il observait (ou qui l'observaient), bien que cela n' empêche nullement à l'un ou l'autre de se désinscrire lui meme dans certaines circonstances , mais que tu dégage surtout aussi bien l'observateur que l'observable d'une responsabilité qu'il n'ont pas à avoir : décider de détruire "l'autre type"
    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

  9. #9
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    Citation Envoyé par Erakis Voir le message
    Qu'arrivera-t-il lorsque l'observateur, (thread différent évidemment) demandera le weak_ptr de l'observable, démarrera un traitement de 10 minutes et que pendant ce temps la variable contenant l'instance de l'observable (démarrage initial) tombe "hors de portée", j'imagine que son destructeur sera appelé ? Et là, bonjour le merdier ?
    Tant que le traitement est actif, mais uniquement pendant ce temps là, l'observateur possède un shared_ptr sur l'observable, qu'il a obtenu à partir du weak_ptr. Donc pendant ce temps, le compteur de référence de l'observable ne peut pas tomber à 0.

    Citation Envoyé par koala01 Voir le message
    L'avantage à travailler de la sorte, c'est que tu "centralise" le point où l'un ou l'autre est détruit, en t'assurant qu'il sera correctement désinscrit de tous les objets qu'il observait (ou qui l'observaient), bien que cela n' empêche nullement à l'un ou l'autre de se désinscrire lui meme dans certaines circonstances , mais que tu dégage surtout aussi bien l'observateur que l'observable d'une responsabilité qu'il n'ont pas à avoir : décider de détruire "l'autre type"
    C'est amusant (je prends le contrepied de ton contrepied), mais cette centralisation lève justement un signal d'alarme chez moi... Pour que ton design marche dans l'environnement multithread du posteur initial, il va falloir ajouter pas mal de locks, ce qui déjà n'est pas trivial, mais en plus, (pas mal de lock + objet centralisé ayant connaissance de tous les autres) => risques de baisses de perfs, d'inversions de priorité...

    Mon design à base de shared/weak_ptr étant décentralisé ne souffre pas de ce problème. Et je ne vois pas de soucis avec le SRP : il n'ajoute pas de responsabilité aux objets :
    - Un observateur connait sans le posséder un observable, c'est la responsabilité de base de l’observateur, et ça se traduit par weak_ptr.
    - Un traitement actif connait et possède (de manière partagée) l'objet auquel se traitement s'applique, ça fait partie de la responsabilité de base du traitement, et ça se traduit à l'aide d'un shared_ptr.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  10. #10
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Par défaut
    koala01 :

    J'aime bien votre idée, mais je programme avec Visual Studio 2008 (Windows CE surtout) et je ne pense pas avoir accès à unique_ptr ? Seulement à auto_ptr. Est-ce que cela fera l'affaire ?

    Autre interrogation quant à votre exemple :
    1. Où instanciez-vous les classes de ***Manager ?
    2. Ces managers ne devraient pas être publique, mais caché non ? Imaginons le nouveau programmeur qui décide de créer une nouvelle classe d'observateur, ce sera bien qu'il est juste à appelé register/unregister. Ne pas avoir à comprendre les manager.

  11. #11
    Membre Expert
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Par défaut
    auto_ptr ne fera pas vraiment l’affaire (ou alors, de manière beaucoup plus complexe). Son utilisation est d’ailleurs de plus en plus déconseillée, car source de beaucoup de problèmes dont ne souffre pas unique_ptr, qui le remplace.

    Sinon, personnellement, je te conseille plutôt la solution de Loïc (weak/shared_ptr, que tu dois avoir avec visual 2008, peut-être dans tr1 par contre), pour les raisons qu’il a évoquées et la beaucoup plus grande simplicité de mise en œuvre.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 634
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    C'est amusant (je prends le contrepied de ton contrepied), mais cette centralisation lève justement un signal d'alarme chez moi... Pour que ton design marche dans l'environnement multithread du posteur initial, il va falloir ajouter pas mal de locks, ce qui déjà n'est pas trivial, mais en plus, (pas mal de lock + objet centralisé ayant connaissance de tous les autres) => risques de baisses de perfs, d'inversions de priorité...
    Je comprends ton point de vue, mais je ne le partage pas forcément:

    La création (d'observateur ou d'observables, vu que c'est le même combat) et l'ajout au niveau du "gestionnaire" peut parfaitement se faire sans lock excessif dans n'importe quel thread, même si l'on peut considérer qu'elle aura vraisemblablement lieu à des moments bien déterminés.

    La destruction des objets mériterait surement beaucoup plus d'attention si tu commences à les détruire "n'importe quand", mais, a priori, tu ne vas pas commencer à le faire, car ce ne devrait jamais être fait "en cours de traitement".

    De plus, la destruction des objets se fera le plus souvent "par lot", les observables et les observés créés pour un traitement particulier étant sans doute détruits en même temps, une fois le traitement effectué.

    Tu peux donc très facilement "centraliser" cette action particulière et veiller à ce qu'elle ne soit entreprise que... lorsque le traitement est terminé, quitte à placer dans tes objet un flag les marquant comme "destructibles".
    Mon design à base de shared/weak_ptr étant décentralisé ne souffre pas de ce problème. Et je ne vois pas de soucis avec le SRP : il n'ajoute pas de responsabilité aux objets :
    - Un observateur connait sans le posséder un observable, c'est la responsabilité de base de l’observateur, et ça se traduit par weak_ptr.
    - Un traitement actif connait et possède (de manière partagée) l'objet auquel se traitement s'applique, ça fait partie de la responsabilité de base du traitement, et ça se traduit à l'aide d'un shared_ptr.
    (emphasis is mine )
    C'est bien cela qui m'embête énormément : La co-propriété de l'objet auquel un (ou plusieurs) traitement s'applique(nt).

    Et c'est sans doute ce qui me déplait le plus dans les shared_ptr!

    Avec des shared_ptr, lorsque tous les propriétaires sont morts, l'objet manipulé est détruit quoi qu'il arrive, même si, pour une raison ou une autre, il aurait pu être maintenu en attente de commencer un traitement suivant.
    Citation Envoyé par Erakis Voir le message
    koala01 :

    J'aime bien votre idée, mais je programme avec Visual Studio 2008 (Windows CE surtout) et je ne pense pas avoir accès à unique_ptr ? Seulement à auto_ptr. Est-ce que cela fera l'affaire ?
    Je te déconseilles fortement auto_ptr, mais tu peux te tourner vers boost pour avoir un unique_ptr (très proche de ceux fournis par la norme C++11.

    Sinon, étant donné que l'idée est de n'avoir qu'un et un seul objet responsable de la durée de vie des objets, tant que tu penses bien à faire appel à delete avant de supprimer tes éléments, un pointeur nu pourrait parfaitement faire l'affaire .

    Le choix d'unique_ptr dans le cas présent étant essentiellement du au coté RAIIsant de la classe (le pointeur sous-jacent étant détruit de manière automatique).

    Autre interrogation quant à votre exemple :
    1. Où instanciez-vous les classes de ***Manager ?
    2. Ces managers ne devraient pas être publique, mais caché non ? Imaginons le nouveau programmeur qui décide de créer une nouvelle classe d'observateur, ce sera bien qu'il est juste à appelé register/unregister. Ne pas avoir à comprendre les manager.
    1- Je crées les gestionnaires en début de programmes, avant le premier traitement, au plus tard "juste avant" la création du premier élément à enregistrer.

    2- *idéalement* cela fonctionnera de toutes manières de concert avec un fabrique : rien, dans toute ton application, ne devant connaître ni les observateurs ni les objets observables par leur type réel (concret), ou plutôt, tout devant travailler avec les abstraction que sont (pour reprendre tes noms de début de discussion) comme étant des CObservateurBase ou des CObservableBase.

    Le polymorphisme est sensé faire le reste

    Et pour ce que le polymorphisme ne fait pas, si tu as effectivement besoin de spécifier un comportement particulier dans un cas bien précis, le double dispatch prendra le relais

    Et donc, pour répondre à ta question (quand même ) la fabrique serait un candidat idéal au fait de s'occuper de l'enregistrement auprès du gestionnaire ad-hoc de manière totalement transparente pour l'utilisateur
    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

  13. #13
    Membre Expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Par défaut
    Pour avoir fait face au problème, en environnement multithread, je rejoins le point de vue de JolyLoïc. L'ajout de locks pour gérer un tel cas est vraiment dommage, les locks sont coûteux et toutes les archis multithreadées n'en utilisent pas (c'est mon cas par exemple, justement pour des raisons de performances). Du coup l'ajout d'un seul lock est déjà excessif. Je suis cependant d'accord avec koala01 sur le point suivant:

    Tu ne peux décemment pas demander à l'observateur de détruire les objets observables qu'il observe, pas plus que tu ne peux demander aux objets observés de détruire les observateurs qui les observent.
    De même, l'usage d'un gestionnaire n'est pas acceptable, car il faut gérer toute une logique de mise en correspondance, ainsi que le cycle de vie du gestionnaire, dont on n'a pas besoin.

    Voici une solution que je propose, même si je ne l'ai pas implémenté exactement comme ça:
    - Les observateurs ne stockent rien : il se contentent de s'enregistrer une fois si l'observable est vivant (y 15000 manières de faire ça, un algo qui essaye d'enregistrer un observer sur un observable mort est probablement foireux).
    - Les observables stockent une std::list<weak_ptr> vers les observateurs. La liste est intéressante car la suppression dedans est peu coûteuse et peut être faite pendant le parcours. On la parcourt toujours en entier, linéairement (ce serait très douteux d'accéder un observateur à un index précis).

    Lorsqu'un observable est détruit, il ne se passe rien : ses observateurs ne seront simplement plus notifiés, et n'ont pas besoin d'être mis à jour.

    Lorsqu'un observateur est détruit, il ne fait rien. Les weak_ptr qui le référence dans les observables s'invalident bien entendu. Lorsque les observables appellent leur notify, il suffit de dégager les weak_ptr invalides de la liste à ce moment là, en profitant de l'inévitable parcours de liste nécessaire pour notifier les observateurs encore vivants (d'où l'intérêt de std::list ici).

    Pour le choix de qui possède les observateurs et les observables, on a liberté totale : les deux mécanismes sont découplés. C'est ultra simple à coder pour ne rien gâcher.

    Je vous fais un exemple en code si mes propose ne sont pas clairs

    Edit: Attention à bien gérer la thread safety des weak_ptr, voir ceci, et pas hésiter à utiliser volatile.

  14. #14
    Rédacteur
    Avatar de Erakis
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Octobre 2003
    Messages
    523
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Octobre 2003
    Messages : 523
    Par défaut
    Bonjour à tous,

    Je vais opté pour l'utlisation des shared_ptr.

    Je réitère les règles :
    1. Un observable peut être détruit en tout temps .
    2. Un observateur être détruit peut en tout temps (attaché ou non)
    3. Un observable doit être en mesure de communiquer (notify) avec ses observateurs.
    4. Un observateur ne peut utiliser les resources d'un observable que s'il y est attaché.
    5. Un observateur s'attache à un observable dans sa boucle de thread ou via l'extérieur (tout dépend du type d'observateur).
    6. Un observateur (dans sa boucle de thread) doit être en mesure d'utiliser les resources de l'observable, à moins que ce dernier soit mort.
    7. Le système doit être rapide et non perdre son temps constemment dans des lock/unlock de syncronisation.

    Voici ce que j'ai jusqu'à présent :

    ProcessBase.h
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class CProcessHolderBase;
     
    class CProcessBase : enable_shared_from_this<CProcessBase>
    {
    public:
    	CProcessBase();
    	virtual ~CProcessBase(void);
     
    	virtual void Notify() = 0;
    private:
    	weak_ptr<CProcessHolderBase> m_pProcessHolderBase;
     
    friend CProcessHolderBase;
    };
    ProcessHolderBase.h
    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
    class CProcessBase;
     
    class CProcessHolderBase : enable_shared_from_this<CProcessHolderBase>
    {
    public:
    	CProcessHolderBase(void);
    	virtual ~CProcessHolderBase(void);
     
    	VOID Attach(CProcessBase* pProcessBase, weak_ptr<CProcessHolderBase>* pProcessHolderSpecial);
    	VOID Detach(CProcessBase* pProcessBase);
     
    private:
    	CCriticalSection		        m_CriticalSectionAttachDetach;
    	vector<weak_ptr<CProcessBase>>		m_vProcessBase;
    };
    ProcessHolderSpecial.h
    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
     
    class CProcessHolderSpecial : public CProcessHolderBase
    {
    public:
    	CProcessHolderSpecial();
    	~CProcessHolderSpecial(void);
     
    	VOID Attach(CProcessBase* pProcessBase, weak_ptr<CProcessHolderSpecial>* pProcessHolderSpecial);
     
    	VOID Detach(CProcessBase* pProcessBase);
     
            INT GetCounter(); // Resource qui peut être utiliser par un ProcessBase (démo)
     
    private:
    	static int m_Counter;  
    };
    ProcessSpecial.h
    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
     
    class CProcessSpecial: public CProcessBase
    {
    public:
    	CProcessSpecial();
    	~CProcessSpecial(void);
     
            void StartProcess(); // Démarrer le thread
            void StopProcess();  // Stopper le thread
     
    	void Update();
     
    	UINT	threadFunction();
            ...
            // J'ai omis toutes les autres variables pour démarrer/gestion le du thread...
    };
    ProcessSpecial.cpp
    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
     
    UINT CProcessSpecial::threadFunction()
    {
       BOOL bIsAttached = FALSE;
       weak_ptr<CProcessHolderBase> pProcessHolderBaseWeak;
       shared_ptr<CProcessHolderBase> pProcessHolderBaseShared;
       CProcessHolderSpecial* pProcessHolderSpecial;
       while (WaitForSingleObject(m_hEventKill, 1000) != WAIT_OBJECT_0)
       {
           if (bIsAttached == FALSE)
           {
    	pProcessHolderBase->Attach(this, pProcessHolderBaseWeak);
                 bIsAttached = TRUE;
           }
     
           if (bIsAttached == TRUE)
           { 
    	pProcessHolderSpecial = dynamic_cast<CProcessHolderSpecial *>(pProcessHolderBase.get());
     
                 // Do intensive work...
    	pProcessHolderSpecial->GetCounter());
     
    	pProcessHolderBase.reset();
           }
       }
    }
    Est-ce que j'ai compris ce que vous me proposer ou suis-je complètement dans le champs ?

    Merci

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

Discussions similaires

  1. Réponses: 5
    Dernier message: 24/02/2010, 16h17
  2. Utilisation du pattern Observateur dans la mise en place d'une architecture MVC
    Par Guyiom dans le forum Langages de programmation
    Réponses: 2
    Dernier message: 25/09/2009, 17h14
  3. [POO] pattern observateur
    Par poukill dans le forum C++
    Réponses: 3
    Dernier message: 16/12/2007, 22h51
  4. Pattern Observateur/ évenements/ découplage
    Par Aïssa dans le forum Windows Forms
    Réponses: 6
    Dernier message: 25/01/2007, 14h30

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