Partage
  • Partager sur Facebook
  • Partager sur Twitter

Du singleton

l'anti-pattern

    23 août 2007 à 1:23:05

    Hello,

    Sous ce titre provocateur, j'extrais à la mano une disgression ayant eu lieu dans le fil sur l'avenir du tuto de C++.

    D'abord, reprennons ce qui a été dit:

    Citation : Nanoc

    Oui une classe Singleton permet de gérer les variables globales de manière moins sale. Mais c'est encore mieux de pas en avoir.



    Citation : lmghs

    Faut pas se leurrer non plus. Un singleton EST une variable globale.


    Citation : pamaury

    Certe mais une variable globale donc la création est gérée de façon unique et par conséquent bien plus contrôlée et sûre qu'une variable globale classique. Enfin c'est mon avis.


    Citation : lmghs

    La variable globale, tu dois la construire dans une et une seule unité de traduction : celle de sa définition.

    Avec le singleton, la fonction instance() qui crée si pas encore créé est bien jolie, mais elle souffre de terribles défauts :
    - cela ne permet pas de paramétrer la construction ;
    - il n'existe aucun moyen sain (pas même le double-lock qui est un faux ami) de retarder la création dans des threads qui peuvent vouloir accéder à la globale en même temps.

    On se retrouve vite à devoir créer explicitement le singleton dans le thread principal de l'application. La différence avec une variable globale est assez limitée du coup.

    (Et n'allez pas me parler de "je ne peux en créer qu'un" quand les deux meilleures implémentations de singletons que je connaisse sont non intrusives (ACE et Loki) -> cette "sécurité" n'apporte rien du tout))



    Citation : Kreeg

    Citation : lmghs

    cela ne permet pas de paramétrer la construction ;



    Ah ?

    1. class foo {
    2.         int _bar;
    3.         foo(int bar) : _bar(bar) { }
    4.     public :
    5.         static foo* get_instance(int bar /* = 0*/);
    6. }
    7. static foo* foo::get_instance(int bar) {
    8.     static foo inst = foo(bar);
    9.     return &inst;
    10. }


    Corrige moi si j'ai mal compris.



    Citation : lmghs

    Et donc sur toutes les utilisations de ton singleton, tu vas lui passer un paramètre ? Comment vas-tu assurer que le bon paramètre est toujours le même, qu'il ne va jamais prendre 3 plombes à être calculé ?



    Sur ce, Kreeg a rajouté une valeur par défaut et reposé sa question (si je ne me trompe pas)


    Et bien ... comment savoir qui construira la globale pour la première fois ? Quel sera le thread responsable de la construction initiale ? Car ce thread là, et seul lui devra disposer du droit de créer l'instance.

    Fort de ces constations, et pour des raisons de clarté et de maitrise de mes invariants, aujourd'hui j'ai tendance à les définir comme cela:
    1. struct Type : private boost::noncopyable
    2. {
    3.     /** Accesseur au singleton.
    4.      * @pre ms_instance != 0
    5.      * @throw None
    6.      */
    7.     static Type& instance() { // à découper .h/.cpp évidemment
    8.         assert(ms_instance && "Singleton Type non initialisé!");
    9.         return *ms_instance;
    10.     }
    11.     /** Construction du singleton.
    12.      * @pre ms_instance == 0
    13.      * @post ms_instance != 0
    14.      * @throw TypeException si la globale ne peut être construite
    15.      */
    16.     static void create(paramètres ...) {
    17.         assert(!ms_instance && "Singleton Type déjà initialisé!");
    18.         return ms_instance = new Type(paramètres...);
    19.     }
    20.     /** Destruction du singleton.
    21.      * @pre ms_instance != 0
    22.      * @post ms_instance == 0
    23.      * @throw None
    24.      */
    25.     void release() {
    26.         assert(ms_instance && "Singleton Type non initialisé!");
    27.         delete ms_instance;
    28.         ms_instance = 0;
    29.     }
    30.     // + fonctions de l'interface publique de la globale
    31. private:
    32.     Type(paramètres...) { ...}
    33.     ~Type() { ...}
    34.     static Type * ms_instance;
    35. };
    36. // dans un et une seul .cpp uniquement
    37. Type* Type::ms_instance = 0;

    Avec un bon éditeur de texte, on a vite fait de disposer de squelette pour générer ce genre de définition.


    Des intérêts que je garde au singleton (comparé aux variables globales classiques):
    - un nom suffisament verbeux pour attirer l'attention sur le caractère unique
    - la possibilité de définir des dépendances entre singletons (notament pour les libérations)
    - pouvoir se la jouer en société ("moi je fais des singletons car les variables globales c'est le mal")


    Bref. Pour bien faire dans une étude du singleton, il faudrait également discuter du pattern monostate, des héritages, ...
    • Partager sur Facebook
    • Partager sur Twitter
    C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
    Anonyme
      23 août 2007 à 1:28:03

      Citation : lmghs

      Et bien ... comment savoir qui construira la globale pour la première fois ? Quel sera le thread responsable de la construction initiale ? Car ce thread là, et seul lui devra disposer du droit de créer l'instance.



      Osef ? (En étant sérieux, je n'ai pas compris ta réponse)

      Je ne réfutais qu'une seule affirmation. Pas toutes.
      • Partager sur Facebook
      • Partager sur Twitter
        23 août 2007 à 1:39:55

        Reprennons. Tout l'intérêt de instance() qui crée le singleton, c'est de ne pas se préoccuper de l'instant de la création (construction parresseuse).

        Or, si tu dois fournir des paramètres de construction, soit tu les fournis partout (chose crade et impossible en particulier dans un environnement multi-thréadé), soit tu les fournis une fois au démarrage de l'appli, et là du coup, pourquoi avoir une fonction qui fait un test pour traiter deux cas de figures au lieu de deux fonctions aux noms des plus explicites ?

        On arrive vite à ma modeste solution. Qui en fait ne fait qu'encapsuler une variable globale juste pour réaliser des test de pré- et post-condition afin d'être sûr que le développeur utilise correctement le singleton (i.e. n'oublie pas de réaliser la construction une et une seule fois). En fait, une solution qui n'essaie pas de résoudre des problèmes pas vraiment pertinents si on a un minimum de méthodologie.
        • Partager sur Facebook
        • Partager sur Twitter
        C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
        Anonyme
          23 août 2007 à 1:56:56

          J'utilise ma 'conception' des classes à instance unique dans mon programme, ça marche très bien. (Je t'invite à lire mon code ici, voire classe Display)

          Jette un oeil à la manière dont j'utilise Display::get_instance() tout au long du programme, ça me convient personnellement parfaitement.

          (Un vrai début de débat sur le SdZ :D )
          (Wah, y'a HanLee :p )
          • Partager sur Facebook
          • Partager sur Twitter
            23 août 2007 à 8:41:48

            Citation : Imghs

            cela ne permet pas de paramétrer la construction ;



            Je dis peut-être nimporte quoi, mais il est tout à fait possible de créer différentes fonctions membres d'initialisation et/ou de lui mettre des paramètres.

            Quelle serait donc selon toi la meilleure chose à faire pour les variables globales ?
            • variables globales ?
            • Classe Singleton ?
            • Namespace ?(bon ça c'est pas super...)
            • autre ?


            La question m'intéresse beaucoup, car c'est un problème récurrent.
            • Partager sur Facebook
            • Partager sur Twitter
            Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
              23 août 2007 à 10:08:44

              Je tiens à rajouter mon petit grain de sable :): pour moi un singleton est utile lorsque l'on ne sait pas du tout si un objet est initialisé ou pas. Il y a des cas où à l'entrée d'une fonction, un objet DOIT être initialisé, dans ces cas là pas besoin de singleton mais sinon le singleton peut-être très pratique.

              Pour ce qui est du namespace, c'est vrai que ce n'est pas forcemment le top mais çà un avantage: lorsque l'on relie le code et que l'on voit un truc du genre:
              1. [...]
              2.   mon_ns::g_ma_var=42;

              Et bien on est immédiatement conscient que l'on manipule une variable globale ! C'est bête à dire mais çà saute plus aux yeux.

              Enfin il y a la solution *ultime*: tout coder de façon à ne pas avoir de variables globales.
              * le maximum est écrit en fonctionnel
              * si un objet A dépend d'un autre B, à la création de A, un pointeur sur B est passé en paramètre. On obtient ainsi une hierarchie de classe dont la super classe est créée dans le main. Eviemment c'est pas toujours possible(dans l'implémentation et le design).

              Enfin ce n'est que mon avis.
              • Partager sur Facebook
              • Partager sur Twitter
                23 août 2007 à 11:43:30

                ce que j'ai dit à l'origine, c'est que pour remplacer les variables globales (dans un projet à plusieurs fichiers, je ne vois pas vraiment de solution pour y accéder facilement de partout) l'utilisation d'un singleton me semble aproprié.
                Si tu te sert de variables globales, tu les initialise généralement dans ton thread principal, et au début (en clair, au début du main ...). Ben avec ton sigleton, c'est la même chose !
                Voilà ce que ça donne :
                1. class VarGlob
                2. {
                3.         public:
                4.                 static VarGlob *m_pInstance;
                5.                 static VarGlob *GetInstance();
                6.                 static void DeleteInstance();
                7.                 int .....
                8.                 char .....
                9.                 .....
                10.                 .....
                11.                 // (on va pas s'enmerder avec des accesseurs pour un simple conteneur de donnée ...)
                12. };
                13. VarGlob *VarGlob::m_pInstance = NULL;
                14. VarGlob *VarGlob::GetInstance()
                15. {
                16.         if(!m_pInstance) m_pInstance = new VarGlob;
                17.         return m_pInstance;
                18. }
                19. void varGlob::DeleteInstance()
                20. {
                21.         if(m_pInstance) delete m_pInstance;
                22.         return m_pInstance;
                23. }
                24. int main()
                25. {
                26.         VarGlob *var = VarGlob::GetInstance();
                27.         var->var1 = 12;
                28.         ....
                29.         ....
                30.         ....
                31.         VarGlob::DeleteInsatnce();
                32.         return 0;
                33. }
                • Partager sur Facebook
                • Partager sur Twitter
                Anonyme
                  23 août 2007 à 11:57:36

                  J'ai pensé une chose.
                  Pourquoi , au lieu d'utiliser des variables globales (singletonée ou pas), ne pas globaliser des variables locales ?
                  Ca donnerai un truc du genre :
                  1. #include <iostream>
                  2. using namespace std;
                  3. template <class T> class Globalisator
                  4. {
                  5. protected:
                  6.     static T* m_instance;
                  7. public:
                  8.     static T* GetInstance()
                  9.     {
                  10.         if(m_instance==0)
                  11.             throw; //ici trow seul pour dire que c'est pas bien de
                  12.                    //ne pas initialiser.Il faudrai définir une
                  13.                    // exeption perso.
                  14.         return m_instance;
                  15.     }
                  16.     static void SetInstance(T* inst)
                  17.     {
                  18.         m_instance=inst;
                  19.     }
                  20.     static void Release()
                  21.     {
                  22.         m_instance=0;
                  23.     }
                  24. };
                  25. template <class T> T* Globalisator<T>::m_instance=0;
                  26. //classe de test
                  27. //aucune utilité
                  28. class Test : public Globalisator<Test>
                  29. {
                  30.     int m_i;
                  31. public:
                  32.     Test():m_i(0){};
                  33.     int Test2(){m_i++;return m_i;}
                  34. };
                  35. //fonction de test
                  36. //Réelle utiité dans des classes
                  37. void fct()
                  38. {
                  39.     cout<<Test::GetInstance()->Test2()<<endl;
                  40. }
                  41. int main (void)
                  42. {
                  43.     Test t1,t2;
                  44. //je fais ca sur des variables locales.
                  45. //Mais comme elles sont locales a main , c'est pas grave.
                  46. //le mieux serait de le faire sur des pointeurs alloué dynamiquement.
                  47.     Test::SetInstance(&t1);
                  48.     fct();
                  49.     fct();
                  50.     Test::SetInstance(&t2);
                  51.     fct();
                  52.     Test::Release();
                  53.     fct();
                  54.     return 0;
                  55. }


                  De cette facon , on peut rendre globale une variable un temps donné sans avoir de problème propre a la modifcation partout.
                  Alors qu'en pensez vous ?

                  Edit: je sais que mon truc c'est du singleton-like mais je tient quand même a dire que ce n'est un singleton,puisque on peut crée autant d'instance qu'on veut et que la classe na pas la gestion de la création et de la destrcution des variables.
                  • Partager sur Facebook
                  • Partager sur Twitter
                  Anonyme
                    23 août 2007 à 14:59:42

                    Salut à tous,

                    le debat est interressant (pour moi, plus sur le fond à savoir le pattern que sur la forme). J'ai une question peut etre bete, vous excuserez ma pietre connaissance du C++, mais ne peut on pas emuler simplement un Singleton, en rendant static l'ensemble de ces membres (et en rendant private son ctor), je sais que c'est possible en Java et en DotNet, mais est ce possible en C++ ?

                    @Davidbrcz : en effet le probleme de ta classe est qu'elle ne gere plus en interne la creation de la classe, donc on pourrait, (par exemple en tant qu'utilisateur de bibliotheques) créer une instance non conforme, par le Set() dans le code. D'ailleurs ce schema, AMHA, se rapproche plus du memento, voire meme du multiton (il manque cependant un Dictionary<key, instance> = map<key, instance> en c++ je crois mais l'idée est la) que du singleton, comme tu le precises d'ailleurs.

                    • Partager sur Facebook
                    • Partager sur Twitter
                    Anonyme
                      23 août 2007 à 15:23:04

                      Citation : SirJulio

                      Salut à tous,

                      le debat est interressant (pour moi, plus sur le fond à savoir le pattern que sur la forme). J'ai une question peut etre bete, vous excuserez ma pietre connaissance du C++, mais ne peut on pas emuler simplement un Singleton, en rendant static l'ensemble de ces membres (et en rendant private son ctor), je sais que c'est possible en Java et en DotNet, mais est ce possible en C++ ?

                      @Davidbrcz : en effet le probleme de ta classe est qu'elle ne gere plus en interne la creation de la classe, donc on pourrait, (par exemple en tant qu'utilisateur de bibliotheques) créer une instance non conforme, par le Set() dans le code. D'ailleurs ce schema, AMHA, se rapproche plus du memento, voire meme du multiton (il manque cependant un Dictionary<key, instance> = map<key, instance> en c++ je crois mais l'idée est la) que du singleton, comme tu le precises d'ailleurs.



                      Pas besoin pour ça de rendre tous les membres statiques, seule la fonction get_instance() doit l'être, ainsi que, bien entendu, toutes les membres dont elle a besoin.
                      • Partager sur Facebook
                      • Partager sur Twitter
                        23 août 2007 à 17:39:54

                        A partir du simple moment où vous dites "Bon on dit que les variables globales sont pas bien donc je vais faire un Singleton ça semble plus classe", pour moi vous êtes dans l'erreur. La plupart des gens utilisent le Singleton à tort et à travers parce que c'est un des pattern les plus simples (quoique : http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html ).

                        On ne doit utiliser un Singleton *que* quand "l'objet à manipuler est unique au système" et pas juste parce que c'est pratique. Et encore même en utilisant la définition il y'a plein de cas sur lequel cela ne s'applique pas. Au première abord on peut penser par exemple qu'un curseur de souris est unique et donc qu'il est adapté à un singleton, sauf que avec les dernière versions de X.Org, par exemple, on a vu apparaître le support de plusieurs source de pointage ce qui rend obsolète le Singleton (pareil pour le display de Kreeg où on peut vouloir balancer la pixmap sur plusieurs écrans à la fois à travers un réseau par exemple). Bon je suis conscient que mon exemple peut être vaporeux mais si vous googlez un peu avec "Singleton anti pattern" vous verrez d'autre cas plus pertinent. De toute façon vouloir paramétrer un Singleton me semble déjà une idée étrange (on est unique ou on l'est pas).

                        AMHA vous aurez beau enrobé votre Singleton dans n'importe qu'elle explication ça restera qu'une variable globale (d'autant que la plupart du temps foutre la classe en statique suffirait). J'ai quand même conscience qu'on a des fois besoin de savoir à un niveau global la valeur d'une variable mais dans ce cas je préfère me créer une classe statique "Runtime" qui possède des get publiques et des set internes (internal) pour récupérer ces valeurs (mais qui peuvent être changé par le composant en charge quand il veut en le notifiant par event le cas échéant).
                        • Partager sur Facebook
                        • Partager sur Twitter
                          27 août 2007 à 10:11:54

                          Citation : Nanoc

                          Citation : Imghs

                          cela ne permet pas de paramétrer la construction ;



                          Je dis peut-être nimporte quoi, mais il est tout à fait possible de créer différentes fonctions membres d'initialisation et/ou de lui mettre des paramètres.

                          Quelle serait donc selon toi la meilleure chose à faire pour les variables globales ?

                          • variables globales ?
                          • Classe Singleton ?
                          • Namespace ?(bon ça c'est pas super...)
                          • autre ?



                          La question m'intéresse beaucoup, car c'est un problème récurrent.


                          La première chose est de ne pas se mentir. Ce n'est pas parce que l'on écrit un singleton que c'est forcément mieux qu'une variable globale. Toutes les critiques faites à l'encontre des variables globales comme quoi elles complexifient le systême (en rajoutant des interdépendances/couplages cachés) s'appliquent également sur les singletons et autre pattern mono-state.

                          Comme je l'ai dit précédemment, dans mes contextes MT, que j'ai fréquemment besoin d'une initialisation, ... ce vers quoi j'ai convergé correspond au code que j'ai donné plus haut.
                          Je garde certains aspects du singleton usuel :
                          - le nom qui évite des confusions avec des variables locales
                          - l'unicité
                          Je rajoute des contraintes de cohérence (invariants) pour forcer l'initialisation ; chose que l'on n'a pas forcément avec des variables globales
                          Je gagne du temps à dégageant les locks

                          (plus sur l'initialisation plus bas)

                          Citation : SirJulio

                          le debat est interressant (pour moi, plus sur le fond à savoir le pattern que sur la forme). J'ai une question peut etre bete, vous excuserez ma pietre connaissance du C++, mais ne peut on pas emuler simplement un Singleton, en rendant static l'ensemble de ces membres (et en rendant private son ctor), je sais que c'est possible en Java et en DotNet, mais est ce possible en C++ ?


                          Bien sûr que c'est possible.
                          C'est ce que l'on appelle le pattern monostate.
                          Sur object mentor il y a un article très intéressant qui compare la pattern singleton au pattern monostate.

                          Citation : Garuma

                          De toute façon vouloir paramétrer un Singleton me semble déjà une idée étrange (on est unique ou on l'est pas).


                          Pour un singleton lié à une application certes.
                          Pour un singleton lié à une bibliothèque, et que chaque application doit utiliser différemment cela a parfaitement du sens. Exemple typique : un singleton de logs qui doit être initialisé avec un nom de fichier de logs qui dépendra de chaque application d'un systême plus complexe. Certes, on peut s'en sortir avec des mutateurs, mais :
                          - je préfère ne pas avoir de singleton construit (et faire exploser un assert) plutôt que de tester un attribut m_isInitialized dans chaque fonction
                          - je suis allergique aux mutateurs quand ils n'agissent que comme paliatif à l'absence de constructeur d'initialisation/présence de constructeur par défaut.

                          Petit avantage de l'approche singletons dans les bibliothèques qui ne s'initialisent que d'une seule façon (genre une simple allocation de ressources globales) : quand on veut pouvoir intégrer cette bibliothèque facilement dans une application, et en particulier au travers d'une autre bibliothèque. L'applicatif ne sera pas forcément au courant qu'une tierce bibliothèque est utilisée et initialisée -- et tant qu'il n'y aura pas d'effet de bord de type MT, tout ira bien.
                          • Partager sur Facebook
                          • Partager sur Twitter
                          C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                            28 avril 2011 à 12:19:19

                            Bonjour,

                            Globale v.s. stngleton

                            1) Lorsque je sais que j'ai besoin d'un truc partagé, je le construit une bonne fois pour toute au début du main (ou en static), puis ensuite tout le monde s'en sert, c'est une variable globale, ça induit des couplages, mais c'est exactement ce qu'on veut.

                            j'ai donc le code suivant:
                            Banana GLOBAL_banana; //ou un pointeur
                            
                            int main(int,char**){
                                GLOBAL_banana = init_banana(); //ou tout autre truc : renvoyer un pointeur, appeler la fonction init d'une classe...
                                GLOBAL_banana.use();
                            }
                            





                            ou avec des statics
                            static Banana & getBanana(){
                                static Banana b(...);
                                return b;
                            }
                            
                            int main(int,char**){
                                getBanana().use()
                            }
                            
                            
                            void ailleurs(){ getBanana().use();}
                            







                            2) Quand
                            • -j'ai besoin d'un truc partagé
                            • -je dois construire le truc partagé UNIQUEMENT si j'en ai besoin
                            • -parfois je n'en ai pas besoin
                            • -parfois j'en ai besoin à plusieurs endroits
                            • -je ne sais pas quel endroit en a besoin en premier

                            J'ai besoin d'un singleton.

                            Dans ce cas, personne n'est responsable de la construction du singleton, mais toutes les fonctions qui l'utilisent ont envie qu'il soit construit quand elles en ont besoin.

                            Comme personne n'est responsable de la construction du singleton, je ne vois pas du tout quel sens il y a à passer des paramètres au constructeur lors de l'utilisation du singleton.

                            La seule chose censée est de faire dépendre le constructeur du singleton d'autres variables globales DEJA initialisées correctement au moment de son appel.

                            Dans ce cas, est ce que le code avec les statics que j'ai écrit plus haut initialise le singleton lors de son premier appel, ou l'initialisation a lieu quelque part de manière obscure à la compilation?





                            3) Bien sur si vous avez un cas concrêt qui ne correspond pas à ces deux autres, je serai heureux que vous me l'expliquez afin que je puisse comprendre ce que vous attendez vraiment d'un singleton

                            L'exemple du log n'est pas vraiment bon :
                            Chaque application a sa copie personnelle avec ses paramettre à elle. C'est un singleton dans chaque appli (unique, initialisé au début) mais ce n'est pas un singleton de la bibliothèque partagé entre toutes les applis.
                            • Partager sur Facebook
                            • Partager sur Twitter

                            Du singleton

                            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                            • Editeur
                            • Markdown