Modèle de conception C ++ Singleton

Récemment, je suis tombé sur une réalisation / implémentation du modèle de conception Singleton pour C ++. Cela a ressemblé à ceci (je l’ai adopté à partir de l’exemple réel):

// a lot of methods are omitted here class Singleton { public: static Singleton* getInstance( ); ~Singleton( ); private: Singleton( ); static Singleton* instance; }; 

De cette déclaration, je peux déduire que le champ d’instance est initié sur le tas. Cela signifie qu’il y a une allocation de mémoire. Ce qui est complètement flou pour moi, c’est quand exactement la mémoire va être libérée? Ou y a-t-il une fuite de bogue et de mémoire? Il semble qu’il y ait un problème dans la mise en œuvre.

Ma principale question est la suivante: comment puis-je le mettre en œuvre correctement?

En 2008, j’ai fourni une implémentation C ++ 98 du modèle de conception Singleton qui est évaluée paresseuse, garantie-destruction, non techniquement adaptée aux threads:
Quelqu’un peut-il me fournir un échantillon de Singleton en c ++?

Voici une implémentation C ++ 11 mise à jour du modèle de conception Singleton qui est évaluée paresseuse, correctement détruite et sécurisée pour les threads .

 class S { public: static S& getInstance() { static S instance; // Guaranteed to be destroyed. // Instantiated on first use. return instance; } private: S() {} // Constructor? (the {} brackets) are needed here. // C++ 03 // ======== // Don't forget to declare these two. You want to make sure they // are unacceptable otherwise you may accidentally get copies of // your singleton appearing. S(S const&); // Don't Implement void operator=(S const&); // Don't implement // C++ 11 // ======= // We can use the better technique of deleting the methods // we don't want. public: S(S const&) = delete; void operator=(S const&) = delete; // Note: Scott Meyers mentions in his Effective Modern // C++ book, that deleted functions should generally // be public as it results in better error messages // due to the comstackrs behavior to check accessibility // before deleted status }; 

Voir cet article sur quand utiliser un singleton: (pas souvent)
Singleton: Comment devrait-il être utilisé

Voir cet article sur l’ordre d’initialisation et comment faire face:
Ordre d’initialisation des variables statiques
Recherche des problèmes d’ordre d’initialisation statiques C ++

Voir cet article décrivant des vies:
Quelle est la durée de vie d’une variable statique dans une fonction C ++?

Voir cet article qui traite de certaines implications pour les singletons:
Instance Singleton déclarée en tant que variable statique de la méthode GetInstance, est-elle sécurisée pour les threads?

Voir cet article qui explique pourquoi le locking à double vérification ne fonctionnera pas sous C ++:
Quels sont les comportements non définis courants qu’un programmeur C ++ doit connaître?
Dr Dobbs: C ++ et les périls du locking à double contrôle: première partie

Étant un Singleton, vous ne voulez généralement pas qu’il soit détruit.

Il sera détruit et désalloué lorsque le programme se termine, ce qui est le comportement normal et souhaité pour un singleton. Si vous voulez pouvoir le nettoyer explicitement, il est assez facile d’append une méthode statique à la classe qui vous permet de la restaurer dans un état propre, et de la réaffecter la prochaine fois qu’elle est utilisée, mais cela ne relève pas du “classique” singleton.

Vous pouvez éviter l’allocation de mémoire. Il existe de nombreuses variantes, toutes présentant des problèmes en cas d’environnement multithreading.

Je préfère ce genre d’implémentation (en fait, ce n’est pas correctement dit, je préfère, car j’évite autant que possible les singletons):

 class Singleton { private: Singleton(); public: static Singleton& instance() { static Singleton INSTANCE; return INSTANCE; } }; 

Il n’a pas d’allocation de mémoire dynamic.

Une autre alternative non-allouante: créez un singleton, disons de la classe C , comme vous en avez besoin:

 singleton() 

en utilisant

 template  X& singleton() { static X x; return x; } 

Ni cette réponse ni celle de Cătălin ne sont automatiquement compatibles avec les threads en C ++ actuel, mais seront en C ++ 0x.

La réponse de @Loki Astari est excellente.

Cependant, il y a des fois des objects statiques multiples où vous devez pouvoir garantir que le singleton ne sera pas détruit tant que tous vos objects statiques utilisant le singleton n’en auront plus besoin.

Dans ce cas, std::shared_ptr peut être utilisé pour maintenir le singleton en vie pour tous les utilisateurs, même lorsque les destructeurs statiques sont appelés à la fin du programme:

 class Singleton { public: Singleton(Singleton const&) = delete; Singleton& operator=(Singleton const&) = delete; static std::shared_ptr instance() { static std::shared_ptr s{new Singleton}; return s; } private: Singleton() {} }; 

Si vous souhaitez allouer l’object en tas, pourquoi ne pas utiliser un pointeur unique. La mémoire sera également libérée puisque nous utilisons un pointeur unique.

 class S { public: static S& getInstance() { if( m_s.get() == 0 ) { m_s.reset( new S() ); } return *m_s; } private: static std::unique_ptr m_s; S(); S(S const&); // Don't Implement void operator=(S const&); // Don't implement }; std::unique_ptr S::m_s(0); 

La solution dans la réponse acceptée présente un inconvénient important: le destructeur du singleton est appelé après que le contrôle ait quitté la fonction “main”. Il peut y avoir des problèmes lorsque certains objects dépendants sont affectés à l’intérieur de “main”.

J’ai rencontré ce problème en essayant d’introduire un Singleton dans l’application Qt. J’ai décidé que tous mes dialogs d’installation devaient être Singletons et adopté le modèle ci-dessus. Malheureusement, la classe principale de Qt “QApplication” a été allouée en stack dans la fonction “main”, et Qt interdit la création / destruction de boîtes de dialog quand aucun object d’application n’est disponible.

C’est pourquoi je préfère les singletons atsortingbués en tas. Je fournis des méthodes explicites “init ()” et “term ()” pour tous les singletons et les appelle à l’intérieur de “main”. J’ai donc un contrôle total sur l’ordre de création / destruction des singletons, et je garantis également que des singletons seront créés, que quelqu’un s’appelle “getInstance ()” ou non.

Voici une implémentation facile.

 #include  #include  using namespace std; class SingletonClass { public: static SingletonClass* getInstance() { return (!m_instanceSingleton) ? m_instanceSingleton = new SingletonClass : m_instanceSingleton; } private: // private constructor and destructor SingletonClass() { cout << "SingletonClass instance created!\n"; } ~SingletonClass() {} // private copy constructor and assignment operator SingletonClass(const SingletonClass&); SingletonClass& operator=(const SingletonClass&); static SingletonClass *m_instanceSingleton; }; SingletonClass* SingletonClass::m_instanceSingleton = nullptr; int main(int argc, const char * argv[]) { SingletonClass *singleton; singleton = singleton->getInstance(); cout << singleton << endl; // Another object gets the reference of the first object! SingletonClass *anotherSingleton; anotherSingleton = anotherSingleton->getInstance(); cout << anotherSingleton << endl; Sleep(5000); return 0; } 

Un seul object créé et cette référence d'object sont renvoyés à chaque fois après les mots.

 SingletonClass instance created! 00915CB8 00915CB8 

Ici 00915CB8 est l'emplacement mémoire de singleton Object, identique pour la durée du programme mais (normalement!) Différent à chaque exécution du programme.

NB Ce n'est pas un filetage sûr. Vous devez vous assurer de la sécurité du fil.

Il est en effet probablement atsortingbué depuis le tas, mais sans les sources, il n’y a aucun moyen de le savoir.

L’implémentation typique (extraite d’un code que j’ai déjà dans emacs) serait:

 Singleton * Singleton::getInstance() { if (!instance) { instance = new Singleton(); }; return instance; }; 

… et comptez sur le programme qui sort de son champ d’action pour faire le ménage par la suite.

Si vous travaillez sur une plate-forme où le nettoyage doit être fait manuellement, j’appendais probablement une routine de nettoyage manuel.

Un autre problème lié à cette façon de faire est qu’il n’est pas sûr pour les threads. Dans un environnement multithread, deux threads pouvaient passer à travers le “if” avant que l’une des deux ait la possibilité d’allouer la nouvelle instance (les deux le seraient). Ce n’est toujours pas une affaire si vous comptez sur la fin du programme pour nettoyer de toute façon.

Ceci concerne la gestion de la durée de vie des objects. Supposons que vous ayez plus que des singletons dans votre logiciel. Et ils dépendent de Logger Singleton. Lors de la destruction de l’application, supposons qu’un autre object singleton utilise Logger pour enregistrer ses étapes de destruction. Vous devez garantir que Logger doit être nettoyé en dernier. Par conséquent, consultez également ce document: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf

Je n’ai pas trouvé d’implémentation du CRTP parmi les réponses, alors voici:

 template class Singleton { public: Singleton() = delete; Singleton(const Singleton &) = delete; Singleton &operator=(const Singleton &) = delete; static HeirT &instance() { static HeirT instance; return instance; } }; 

Pour utiliser juste hériter de votre classe de ceci, comme: class Test : public Singleton

Est-ce que quelqu’un a mentionné std::call_once et std::once_flag ? La plupart des autres approches – y compris le double contrôle du locking – sont rompues.

Un problème majeur dans l’implémentation du modèle singleton est l’initialisation en toute sécurité. Le seul moyen sûr consiste à protéger la séquence d’initialisation avec des barrières de synchronisation. Mais ces obstacles eux-mêmes doivent être instaurés en toute sécurité. std::once_flag est le mécanisme permettant d’obtenir une initialisation sécurisée.

 #define INS(c) private:void operator=(c const&){};public:static c& I(){static c _instance;return _instance;} 

Exemple:

  class CCtrl { private: CCtrl(void); virtual ~CCtrl(void); public: INS(CCtrl); 

En plus de l’autre discussion, il peut être intéressant de noter que vous pouvez avoir une globalité sans limiter votre utilisation à une seule instance. Par exemple, considérons le cas de référence en comptant quelque chose …

 struct Store{ std::array data; size_t get(size_t idx){ /* ... */ } void incr_ref(size_t idx){ /* ... */} void decr_ref(size_t idx){ /* ... */} }; template struct ItemRef{ size_t idx; auto get(){ return store_p->get(idx); }; ItemRef() { store_p->incr_ref(idx); }; ~ItemRef() { store_p->decr_ref(idx); }; }; Store store1_g; Store store2_g; // we don't ressortingct the number of global Store instances 

Maintenant, quelque part dans une fonction (telle que main ), vous pouvez faire:

 auto ref1_a = ItemRef<&store1_g>(101); auto ref2_a = ItemRef<&store2_g>(201); 

Les refs n’ont pas besoin de stocker un pointeur dans leur Store respectif car ces informations sont fournies à la compilation. Vous n’avez pas non plus à vous soucier de la durée de vie du Store , car le compilateur exige qu’il soit global. S’il n’y a en effet qu’une seule instance de Store il n’y a pas de surcharge dans cette approche; avec plus d’une instance, le compilateur doit être intelligent en matière de génération de code. Si nécessaire, la classe ItemRef peut même devenir un friend de Store (vous pouvez avoir des amis ItemRef !).

Si Store lui-même est une classe basée sur des modèles, les choses deviennent plus compliquées, mais il est toujours possible d’utiliser cette méthode, peut-être en implémentant une classe d’assistance avec la signature suivante:

 template  struct StoreWrapper{ /* stuff to access store_p, eg methods returning instances of ItemRef. */ }; 

L’utilisateur peut désormais créer un type StoreWrapper (et une instance globale) pour chaque instance de Store global et accéder toujours aux magasins via leur instance wrapper (oubliant ainsi les détails gores des parameters de modèle nécessaires à l’utilisation de Store ).

Je pense que vous devriez écrire une fonction statique dans laquelle votre object statique est supprimé. Vous devez appeler cette fonction lorsque vous êtes sur le sharepoint fermer votre application. Cela garantira que vous n’avez pas de fuite de mémoire.

Le papier lié à ce qui précède décrit l’inconvénient du locking à double vérification: le compilateur peut allouer la mémoire pour l’object et définir un pointeur sur l’adresse de la mémoire allouée avant l’appel du constructeur de l’object. Il est cependant assez facile en c ++ d’utiliser des allocaters pour allouer la mémoire manuellement, puis utiliser un appel de construction pour initialiser la mémoire. Grâce à cette parsing, le locking à double vérification fonctionne correctement.

Que diriez-vous d’utiliser un nouvel emplacement comme ceci:

 class singleton { static singleton *s; static unsigned char *buffer[sizeof(singleton)/4 *4] //4 byte align static singleton* getinstance() { if (s == null) { s = new(buffer) singleton; } return s; } };