Récemment, je suis tombé sur une réalisation / implémentation du modèle de conception Singleton pour C ++. Cela ressemble à ceci (je l'ai adopté à partir de l'exemple de la vie réelle):
// 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 n'est pas clair pour moi, c'est quand exactement la mémoire va être désallouée? Ou y a-t-il un bug et une fuite de mémoire? Il semble qu'il y ait un problème dans la mise en œuvre.
Ma principale question est, comment puis-je l'implémenter de la bonne manière?
c++
design-patterns
singleton
Artem Barger
la source
la source
Réponses:
En 2008, j'ai fourni une implémentation C ++ 98 du modèle de conception Singleton qui est évaluée paresseusement, à destruction garantie et non techniquement sécurisée pour les 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 paresseusement, correctement détruite et thread-safe .
Consultez cet article pour savoir quand utiliser un singleton: (pas souvent)
Singleton: comment doit-il être utilisé
Consultez ces deux articles sur l'ordre d'initialisation et comment y faire face:
Ordre d'initialisation des variables statiques
Recherche des problèmes d'ordre d'initialisation statique C ++
Consultez cet article décrivant les durées de vie:
Quelle est la durée de vie d'une variable statique dans une fonction C ++?
Consultez cet article qui traite de certaines implications de threading pour les singletons: l'
instance Singleton déclarée comme variable statique de la méthode GetInstance, est-elle thread-safe?
Consultez cet article qui explique pourquoi le verrouillage à double vérification ne fonctionnera pas sur C ++:
Quels sont tous les comportements non définis courants qu'un programmeur C ++ devrait connaître?
Dr Dobbs: C ++ et les dangers du verrouillage à double vérification: première partie
la source
What irks me most though is the run-time check of the hidden boolean in getInstance()
C'est une hypothèse sur la technique de mise en œuvre. Il n'est pas nécessaire de supposer qu'il est vivant. voir stackoverflow.com/a/335746/14065 Vous pouvez forcer une situation afin qu'elle soit toujours vivante (moins de frais généraux queSchwarz counter
). Les variables globales ont plus de problèmes avec l'ordre d'initialisation (entre les unités de compilation) car vous ne forcez pas un ordre. L'avantage de ce modèle est 1) l'initialisation paresseuse. 2) Capacité à exécuter une ordonnance (Schwarz aide mais est plus laid). Oui,get_instance()
c'est beaucoup plus laid.Étant un Singleton, vous ne voulez généralement pas qu'il soit détruit.
Il sera démoli et désalloué à la fin du programme, ce qui est le comportement normal et souhaité pour un singleton. Si vous voulez pouvoir le nettoyer explicitement, il est assez facile d'ajouter une méthode statique à la classe qui vous permet de la restaurer dans un état propre et de la réallouer la prochaine fois qu'elle est utilisée, mais cela sort du cadre d'un singleton "classique".
la source
Vous pourriez éviter l'allocation de mémoire. Il existe de nombreuses variantes, toutes ayant des problèmes en cas d'environnement multithreading.
Je préfère ce type d'implémentation (en fait, ce n'est pas correctement dit que je préfère, car j'évite les singletons autant que possible):
Il n'a pas d'allocation de mémoire dynamique.
la source
@Loki Astari's answer est excellente.
Cependant, il y a des moments avec plusieurs objets statiques où vous devez être en mesure de garantir que le singleton ne sera pas détruit jusqu'à ce que tous vos objets statiques qui utilisent le singleton n'en plus besoin.
Dans ce cas,
std::shared_ptr
peut être utilisé pour maintenir le singleton vivant pour tous les utilisateurs même lorsque les destructeurs statiques sont appelés à la fin du programme:la source
Une autre alternative sans allocation: créez un singleton, disons de classe
C
, comme vous en avez besoin:en utilisant
Ni ceci ni la réponse de Cătălin ne sont automatiquement thread-safe dans le C ++ actuel, mais le seront en C ++ 0x.
la source
Je n'ai pas trouvé d'implémentation CRTP parmi les réponses, alors voici:
Pour utiliser, héritez simplement de votre classe, comme:
class Test : public Singleton<Test>
la source
La solution de la réponse acceptée présente un inconvénient important: le destructeur du singleton est appelé une fois que le contrôle a quitté la
main()
fonction. Il peut y avoir vraiment des problèmes lorsque certains objets dépendants sont alloués à l'intérieurmain
.J'ai rencontré ce problème en essayant d'introduire un Singleton dans l'application Qt. J'ai décidé que toutes mes boîtes de dialogue de configuration devaient être des singletons et j'ai adopté le modèle ci-dessus. Malheureusement, la classe principale de Qt a
QApplication
été allouée sur pile dans lamain
fonction, et Qt interdit de créer / détruire des boîtes de dialogue quand aucun objet d'application n'est disponible.C'est pourquoi je préfère les singletons alloués en tas. Je fournis une explicite
init()
et desterm()
méthodes pour tous les singletons et les appelle à l'intérieurmain
. Ainsi, j'ai un contrôle total sur l'ordre de création / destruction des singletons, et je garantis également que les singletons seront créés, que quelqu'un l'appellegetInstance()
ou non.la source
Voici une mise en œuvre facile.
Un seul objet créé et cette référence d'objet est renvoyée à chaque fois par la suite.
Ici 00915CB8 est l'emplacement mémoire de l'objet singleton, identique pour la durée du programme mais (normalement!) Différent à chaque exécution du programme.
NB Ce n'est pas un fil sûr. Vous devez assurer la sécurité du fil.
la source
Si vous souhaitez allouer l'objet en tas, pourquoi ne pas utiliser un pointeur unique. La mémoire sera également désallouée car nous utilisons un pointeur unique.
la source
m_s
un localstatic
degetInstance()
et l'initialiser immédiatement sans test.Il est en effet probablement attribué à partir du tas, mais sans les sources, il n'y a aucun moyen de le savoir.
L'implémentation typique (tirée d'un code que j'ai déjà dans emacs) serait:
... et comptez sur le programme hors de portée pour le nettoyer ensuite.
Si vous travaillez sur une plate-forme où le nettoyage doit être effectué manuellement, j'ajouterais probablement une routine de nettoyage manuel.
Un autre problème avec cette méthode est qu'elle n'est pas adaptée aux threads. Dans un environnement multithread, deux threads pourraient passer par le "si" avant que l'un ou l'autre n'ait la possibilité d'allouer la nouvelle instance (donc les deux le feraient). Ce n'est toujours pas trop grave si vous comptez sur la fin du programme pour nettoyer de toute façon.
la source
Quelqu'un a-t-il mentionné
std::call_once
etstd::once_flag
? La plupart des autres approches - y compris le verrouillage à double vérification - sont rompues.Un problème majeur dans l'implémentation d'un modèle singleton est l'initialisation sûre. Le seul moyen sûr est de protéger la séquence d'initialisation avec des barrières de synchronisation. Mais ces obstacles eux-mêmes doivent être mis en place en toute sécurité.
std::once_flag
est le mécanisme pour obtenir une initialisation sûre et garantie.la source
Nous avons récemment abordé ce sujet dans ma classe EECS. Si vous voulez regarder les notes de cours en détail, visitez http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf
Il y a deux façons que je connais de créer correctement une classe Singleton.
Première voie:
Implémentez-le de la même manière que vous l'avez dans votre exemple. Quant à la destruction, «les singletons durent généralement pendant la durée de l'exécution du programme; la plupart des systèmes d'exploitation récupèrent la mémoire et la plupart des autres ressources à la fin d'un programme, il y a donc un argument pour ne pas s'inquiéter de cela».
Cependant, il est recommandé de nettoyer à la fin du programme. Par conséquent, vous pouvez le faire avec une classe SingletonDestructor statique auxiliaire et le déclarer comme ami dans votre Singleton.
Le Singleton_destroyer sera créé au démarrage du programme et "lorsque le programme se termine, tous les objets globaux / statiques sont détruits par le code d'arrêt de la bibliothèque d'exécution (inséré par l'éditeur de liens), donc le_destroyer sera détruit; son destructeur supprimera le Singleton, exécutant son destructeur. "
Deuxième voie
C'est ce qu'on appelle le Meyers Singleton, créé par l'assistant C ++ Scott Meyers. Définissez simplement get_instance () différemment. Maintenant, vous pouvez également vous débarrasser de la variable membre du pointeur.
C'est bien car la valeur retournée est par référence et vous pouvez utiliser la
.
syntaxe au lieu d'->
accéder aux variables membres."Le compilateur crée automatiquement du code qui crée 's' pour la première fois via la déclaration, pas par la suite, puis supprime l'objet statique à la fin du programme."
Notez également qu'avec le Meyers Singleton, vous "pouvez vous retrouver dans une situation très difficile si les objets dépendent les uns des autres au moment de la terminaison - quand le Singleton disparaît-il par rapport à d'autres objets? Mais pour les applications simples, cela fonctionne très bien".
la source
En plus de l'autre discussion ici, il peut être intéressant de noter que vous pouvez avoir une globalité, sans limiter l'utilisation à une seule instance. Par exemple, considérons le cas d'une référence comptant quelque chose ...
Maintenant, quelque part dans une fonction (comme
main
), vous pouvez faire:Les arbitres n'ont pas besoin de stocker un pointeur sur leurs respectifs
Store
car ces informations sont fournies au moment de la compilation. Vous n'avez pas non plus à vous soucier de laStore
durée de vie du car le compilateur requiert qu'il soit global. S'il n'y a en effet qu'une seule instance,Store
il n'y a pas de frais généraux dans cette approche; avec plus d'une instance, c'est au compilateur d'être intelligent sur la génération de code. Si nécessaire, laItemRef
classe peut même être unfriend
desStore
(vous pouvez avoir des amis templated!).Si
Store
elle-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:L'utilisateur peut désormais créer un
StoreWrapper
type (et une instance globale) pour chaqueStore
instance globale , et toujours accéder aux magasins via leur instance d'encapsuleur (oubliant ainsi les détails sanglants des paramètres de modèle nécessaires à l'utilisationStore
).la source
Il s'agit de la gestion de la durée de vie des objets. Supposons que vous ayez plus que des singletons dans votre logiciel. Et ils dépendent de Logger singleton. Pendant la destruction de l'application, supposons qu'un autre objet singleton utilise Logger pour consigner ses étapes de destruction. Vous devez garantir que l'enregistreur doit être nettoyé en dernier. Par conséquent, veuillez également consulter ce document: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf
la source
Mon implémentation est similaire à celle de Galik. La différence est que mon implémentation permet aux pointeurs partagés de nettoyer la mémoire allouée, au lieu de conserver la mémoire jusqu'à ce que l'application soit fermée et que les pointeurs statiques soient nettoyés.
la source
Votre code est correct, sauf que vous n'avez pas déclaré le pointeur d'instance en dehors de la classe . Les déclarations de classe interne de variables statiques ne sont pas considérées comme des déclarations en C ++, mais cela est autorisé dans d'autres langages comme C # ou Java, etc.
Vous devez savoir que l'instance Singleton n'a pas besoin d'être supprimée manuellement par nous . Nous en avons besoin d'un seul objet tout au long du programme, donc à la fin de l'exécution du programme, il sera automatiquement désalloué.
la source
Le document qui a été lié à ci-dessus décrit l'inconvénient du verrouillage à double vérification est que le compilateur peut allouer la mémoire pour l'objet et définir un pointeur sur l'adresse de la mémoire allouée, avant l'appel du constructeur de l'objet. Cependant, il est assez facile en c ++ d'utiliser des allocateurs pour allouer manuellement la mémoire, puis d'utiliser un appel de construction pour initialiser la mémoire. En utilisant cette approche, le verrouillage à double vérification fonctionne très bien.
la source
Exemple:
la source
Classe simple singleton, ce doit être votre fichier de classe d'en-tête
Accédez à votre singleton comme ceci:
la source
Je pense que vous devriez écrire une fonction statique dans laquelle votre objet statique est supprimé. Vous devez appeler cette fonction lorsque vous êtes sur le point de fermer votre application. Cela garantira que vous n'avez pas de fuite de mémoire.
la source