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

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  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
    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.

  4. #4
    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.

  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 :

    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é.

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

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    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

  8. #8
    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.

  9. #9
    Membre éclairé
    Avatar de Zenol
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2004
    Messages
    812
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    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.

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

    Informations professionnelles :
    Activité : aucun

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

    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

+ 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