Singleton: comment doit-il être utilisé

291

Edit: À partir d'une autre question, j'ai fourni une réponse qui contient des liens vers de nombreuses questions / réponses sur les singletons: Plus d'informations sur les singletons ici:

J'ai donc lu le fil Singletons: un bon design ou une béquille?
Et l'argument fait toujours rage.

Je vois les singletons comme un modèle de conception (bon et mauvais).

Le problème avec Singleton n'est pas le Pattern mais plutôt les utilisateurs (désolé tout le monde). Tout le monde et leur père pensent qu'ils peuvent en appliquer un correctement (et d'après les nombreuses interviews que j'ai faites, la plupart des gens ne le peuvent pas). Aussi parce que tout le monde pense pouvoir implémenter un Singleton correct, ils abusent du Pattern et l'utilisent dans des situations qui ne sont pas appropriées (en remplaçant les variables globales par des Singletons!).

Les principales questions auxquelles il faut répondre sont donc:

  • Quand devez-vous utiliser un Singleton
  • Comment implémenter correctement un singleton

Mon espoir pour cet article est que nous puissions rassembler ensemble en un seul endroit (plutôt que d'avoir à rechercher sur Google et plusieurs sites) une source faisant autorité pour savoir quand (et ensuite comment) utiliser correctement un Singleton. Une liste d'anti-utilisations et de mauvaises implémentations courantes serait également appropriée, expliquant pourquoi elles ne fonctionnent pas et pour de bonnes implémentations, leurs faiblesses.


Alors lancez le ballon:
je vais tenir ma main et dire que c'est ce que j'utilise mais a probablement des problèmes.
J'aime le traitement du sujet par "Scott Myers" dans ses livres "Effective C ++"

Bonnes situations pour utiliser des singletons (pas beaucoup):

  • Cadres de journalisation
  • Pools de recyclage de fils
/*
 * C++ Singleton
 * Limitation: Single Threaded Design
 * See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
 *      For problems associated with locking in multi threaded applications
 *
 * Limitation:
 * If you use this Singleton (A) within a destructor of another Singleton (B)
 * This Singleton (A) must be fully constructed before the constructor of (B)
 * is called.
 */
class MySingleton
{
    private:
        // Private Constructor
        MySingleton();
        // Stop the compiler generating methods of copy the object
        MySingleton(MySingleton const& copy);            // Not Implemented
        MySingleton& operator=(MySingleton const& copy); // Not Implemented

    public:
        static MySingleton& getInstance()
        {
            // The only instance
            // Guaranteed to be lazy initialized
            // Guaranteed that it will be destroyed correctly
            static MySingleton instance;
            return instance;
        }
};

D'ACCORD. Permet de rassembler quelques critiques et autres implémentations.
:-)

Loki Astari
la source
36
Que se passe-t-il si vous décidez plus tard de vouloir plusieurs enregistreurs? Ou plusieurs pools de threads? Si vous ne voulez qu'un seul enregistreur, ne créez qu'une seule instance et rendez-la globale. Les singletons ne sont bons que si vous en avez absolument besoin pour n'en être qu'un et qu'il a besoin d'être mondial, à mon humble avis.
3
Qui a dit qu'un framework ne peut avoir qu'une seule instance de logger. Un singelton représentant Framework. Framwork peut alors vous fournir des enregistreurs spécifiques.
Martin York
Oui. Je n'utiliserais pas un singeltong comme threadpool. Il suffit de lancer des idées pour susciter des réponses.
Martin York
@Dan Singleton qui implémente un modèle de stratégie. Le comportement est abstrait de singleton. Singleton est un point d'entrée unique. Ne pas avoir deux enregistreurs, avoir un enregistreur qui peut décider comment se connecter. Vous ne pouvez pas uniquement générer un journal à la fois, pas besoin d'en avoir deux.
Lee Louviere
5
Xaade: que faire si vous souhaitez vous connecter à deux fichiers? Ou vers une base de données? Ou une prise réseau? Ou un widget GUI? Le fait est, n'ajoutez pas de restrictions artificielles - ce n'est pas nécessaire. Combien de fois avez-vous déjà accidentellement créé deux boucles pour au lieu d'une seule? Si vous ne voulez qu'un seul enregistreur, n'en créez qu'un.

Réponses:

182

Vous avez tous tort. Lisez la question. Répondre:

Utilisez un singleton si:

  • Vous devez avoir un et un seul objet d'un type dans le système

N'utilisez pas un Singleton si:

  • Vous voulez économiser de la mémoire
  • Vous voulez essayer quelque chose de nouveau
  • Vous voulez montrer ce que vous savez
  • Parce que tout le monde le fait (Voir programmeur culte cargo dans wikipedia)
  • Dans les widgets de l'interface utilisateur
  • C'est censé être un cache
  • En cordes
  • En sessions
  • Je peux y aller toute la journée

Comment créer le meilleur singleton:

  • Plus c'est petit, mieux c'est. Je suis minimaliste
  • Assurez-vous qu'il est sûr pour les threads
  • Assurez-vous qu'il n'est jamais nul
  • Assurez-vous qu'il n'est créé qu'une seule fois
  • Initialisation paresseuse ou système? Jusqu'à vos exigences
  • Parfois, le système d'exploitation ou la JVM crée des singletons pour vous (par exemple, en Java, chaque définition de classe est un singleton)
  • Fournir un destructeur ou comprendre comment éliminer les ressources
  • Utilisez peu de mémoire
Javaxpert
la source
14
En fait, je pense que vous n'avez pas tout à fait raison non plus. Je reformulerais comme suit: "Si vous avez besoin d'avoir un et un seul objet d'un type dans le système ET que vous devez y avoir un accès global" L'accent sur le besoin est le mien - ne le faites pas si c'est pratique, seulement si vous DOIT l'avoir.
91
Tu te trompes aussi. Si vous avez besoin d'un et d'un seul objet, vous en créez un et un seul. S'il n'y a aucun moyen logique que deux instances puissent être hébergées sans corrompre irréversiblement l'application, vous devriez envisager d'en faire un singleton. Et puis il y a l'autre aspect, l'accès global: si vous n'avez pas besoin d'un accès global à l'instance, ce ne devrait pas être un singleton.
jalf
4
Fermé pour modification, ouvert pour extension. Le problème est que vous ne pouvez pas étendre un singleton pour qu'il soit un duoton ou un tripleton. Il est coincé comme un singleton.
Lee Louviere
2
@ enzom83: Un singleton majuscule-S inclut du code pour garantir son unicité. Si vous ne voulez qu'une seule instance, vous pouvez perdre ce code et créer simplement une instance vous-même ... ce qui vous permet d'économiser la mémoire d'une seule instance, ainsi que les économies de l'obviation du code qui applique l'unicité - ce qui signifie également ne pas sacrifier le possibilité de créer une deuxième instance si jamais vos besoins changent.
cHao
4
"Si vous avez besoin d'avoir un et un seul objet d'un type dans le système" - "... et que vous ne voulez jamais vous moquer de cet objet dans un test unitaire."
Cygon
72

Les singletons vous donnent la possibilité de combiner deux mauvais traits dans une classe. C'est faux à peu près dans tous les sens.

Un singleton vous donne:

  1. Accès global à un objet, et
  2. Une garantie qu'un seul objet de ce type ne pourra jamais être créé

Le numéro un est simple. Les globaux sont généralement mauvais. Nous ne devons jamais rendre les objets accessibles au monde à moins d'en avoir vraiment besoin.

Le numéro deux peut sembler logique, mais réfléchissons-y. À quand remonte la dernière fois que vous avez ** accidentellement * créé un nouvel objet au lieu de référencer un existant? Comme il s'agit du tag C ++, utilisons un exemple de ce langage. Ecrivez-vous souvent accidentellement

std::ostream os;
os << "hello world\n";

Quand vous aviez l'intention d'écrire

std::cout << "hello world\n";

Bien sûr que non. Nous n'avons pas besoin de protection contre cette erreur, car ce type d'erreur ne se produit tout simplement pas. Si c'est le cas, la bonne réponse est de rentrer à la maison et de dormir pendant 12 à 20 heures et j'espère que vous vous sentirez mieux.

Si un seul objet est nécessaire, créez simplement une instance. Si un objet doit être accessible globalement, faites-en un global. Mais cela ne signifie pas qu'il devrait être impossible d'en créer d'autres instances.

La contrainte «une seule instance est possible» ne nous protège pas vraiment contre les bogues probables. Mais il ne fait notre code très difficile à refactor et à entretenir. Parce que très souvent, nous découvrons plus tard que nous avions besoin de plus d'une instance. Nous n'avons plus d'une base de données, nous ne l' avons plus d'un objet de configuration, nous ne voulons plusieurs enregistreurs. Nos tests unitaires peuvent vouloir créer et recréer ces objets à chaque test, pour prendre un exemple courant.

Donc , un singleton doit être utilisé si et seulement si, nous avons besoin à la fois les caractéristiques qu'il offre: Si nous devons un accès mondial ( ce qui est rare, parce que globals sont généralement découragés) et nous avons besoin pour empêcher quiconque de jamais créer plus d'une instance d'un classe (ce qui me semble être un problème de conception). La seule raison pour laquelle je peux voir cela est si la création de deux instances corromprait l'état de notre application - probablement parce que la classe contient un certain nombre de membres statiques ou une bêtise similaire. Dans ce cas, la réponse évidente est de corriger cette classe. Cela ne devrait pas dépendre d'être la seule instance.

Si vous avez besoin d'un accès global à un objet, faites-en un global, comme std::cout. Mais ne limitez pas le nombre d'instances qui peuvent être créées.

Si vous avez absolument besoin de limiter le nombre d'instances d'une classe à une seule, et qu'il n'y a aucun moyen de créer une deuxième instance en toute sécurité, appliquez-le. Mais ne le rendez pas également accessible à l'échelle mondiale.

Si vous avez besoin des deux traits, 1) faites-en un singleton et 2) faites-moi savoir pourquoi vous en avez besoin, car j'ai du mal à imaginer un tel cas.

jalf
la source
3
ou vous pouvez faire un monde, et d' obtenir que l' un des inconvénients d'un singleton. Avec le singleton, vous vous limiteriez simultanément à une instance de ce groupe de bases de données. Pourquoi faire ça? Ou vous pouvez voir pourquoi vous avez tant de dépendances que la liste d'instanciation devient "vraiment longue". Sont-ils tous nécessaires? Certains d'entre eux devraient-ils être délégués à d'autres composants? Peut-être que certains d'entre eux pourraient être regroupés dans une structure afin que nous puissions les passer comme un seul argument. Il existe de nombreuses solutions, toutes meilleures que les singletons.
jalf
6
Oui, un singleton pourrait y être justifié. Mais je pense que vous venez de prouver mon point de vue qu'il n'est nécessaire que dans des cas assez exotiques. La plupart des logiciels ne traitent pas du matériel de déneigement. Mais je ne suis toujours pas convaincu. Je suis d'accord que dans votre candidature réelle, vous ne voulez qu'un seul d'entre eux. Mais qu'en est-il de vos tests unitaires? Chacun d'eux doit s'exécuter de manière isolée, de sorte qu'ils devraient idéalement créer leur propre SpreaderController - ce qui est difficile à faire avec un singleton. Enfin, pourquoi vos collègues créeraient-ils plusieurs instances en premier lieu? Est-ce un scénario réaliste contre lequel se protéger?
jalf
3
Et un point que vous avez manqué, c'est que même si vos deux derniers exemples justifient sans doute la limitation "une seule instance", ils ne font rien pour justifier celle "globalement accessible". Pourquoi diable la base de code entière devrait-elle pouvoir accéder à l'unité d'administration de votre commutateur téléphonique? Le point dans un singleton est de vous donner les deux traits. Si vous avez juste besoin de l'un ou de l'autre, vous ne devez pas utiliser de singleton.
jalf
2
@ jalf - Mon objectif était juste de vous donner un exemple de l'endroit où Singleton est utile dans la nature, car vous ne pouviez pas en imaginer; Je suppose que vous ne voyez pas trop de fois l'appliquer à votre ligne de travail actuelle. Je suis passé à la programmation de chasse-neige à partir d'applications commerciales uniquement parce que cela me permettrait d'utiliser Singleton. :) j / k Je suis d'accord avec votre prémisse qu'il existe de meilleures façons de faire ces choses, vous m'avez beaucoup donné à réfléchir. Merci pour la discussion!
J.Polfer
2
Utiliser le "modèle" singleton ( AHEM! ) Pour empêcher les gens d'instancier plus d'instances est tout simplement stupide juste pour empêcher les gens de le faire de manière accidentelle. Lorsque j'ai une variable locale foo1 de type Foo dans ma petite fonction et que je n'en veux qu'une dans la fonction, je ne crains pas que quelqu'un crée un second foo varaible foo2 et l'utilise à la place de l'original.
Thomas Eding
36

Le problème avec les singletons n'est pas leur mise en œuvre. C'est qu'ils confondent deux concepts différents, dont aucun n'est évidemment souhaitable.

1) Les singletons fournissent un mécanisme d'accès global à un objet. Bien qu'ils puissent être légèrement plus sûrs pour les threads ou légèrement plus fiables dans les langues sans ordre d'initialisation bien défini, cette utilisation est toujours l'équivalent moral d'une variable globale. C'est une variable globale habillée d'une syntaxe maladroite (foo :: get_instance () au lieu de g_foo, disons), mais elle sert exactement le même but (un seul objet accessible dans tout le programme) et a exactement les mêmes inconvénients.

2) Les singletons empêchent les instanciations multiples d'une classe. Il est rare, IME, que ce type de fonctionnalité soit intégré dans une classe. C'est normalement une chose beaucoup plus contextuelle; beaucoup de choses qui sont considérées comme une et une seule ne sont en réalité que des choses qui ne sont qu’une seule. OMI, une solution plus appropriée consiste à ne créer qu'une seule instance - jusqu'à ce que vous réalisiez que vous avez besoin de plusieurs instances.

DrPizza
la source
6
D'accord. Deux torts peuvent faire un droit selon certains, dans le monde réel. Mais en programmation, mélanger deux mauvaises idées ne se traduit pas par une bonne.
jalf
27

Une chose avec les modèles: ne généralisez pas . Ils ont tous les cas où ils sont utiles et quand ils échouent.

Singleton peut être désagréable lorsque vous devez tester le code. Vous êtes généralement coincé avec une instance de la classe et pouvez choisir entre ouvrir une porte dans le constructeur ou une méthode pour réinitialiser l'état, etc.

Un autre problème est que le Singleton n'est en fait rien de plus qu'une variable globale déguisée. Lorsque vous avez trop d'état partagé global sur votre programme, les choses ont tendance à revenir en arrière, nous le savons tous.

Cela peut rendre le suivi des dépendances plus difficile. Lorsque tout dépend de votre Singleton, il est plus difficile de le changer, de le diviser en deux, etc. Vous êtes généralement coincé avec. Cela entrave également la flexibilité. Enquêter sur un cadre d' injection de dépendance pour tenter de résoudre ce problème.

Paweł Hajdan
la source
8
Non, un singleton est bien plus qu'une variable globale déguisée. C'est ce qui le rend particulièrement mauvais. Il combine la globalité (qui est généralement mauvaise) avec un autre concept qui est également mauvais (celui de ne pas laisser le programmeur instancier une classe s'il décide qu'il a besoin d'une instance) .Ils sont souvent utilisés comme variables globales, oui. Et puis ils entraînent également l'autre effet secondaire désagréable, et paralysent la base de code.
jalf
7
Il convient également de noter que les singletons n'ont pas à être accessibles au public. Un singleton peut très bien être interne à la bibliothèque et jamais exposé à l'utilisateur. Ils ne sont donc pas nécessairement «mondiaux» dans ce sens.
Steven Evers
1
+1 pour avoir souligné la douleur que les Singletons éprouvent.
DevSolar
1
@jalf Ne pas autoriser quelqu'un à créer plusieurs instances d'une classe n'est pas une mauvaise chose. S'il n'y a vraiment qu'une seule instance de la classe instanciée, cela applique l'exigence. Si quelqu'un décide plus tard qu'il doit créer une autre instance, il doit la refactoriser, car il n'aurait jamais dû être un singleton en premier lieu.
William
2
@William: et j'ai dû avoir plusieurs enregistreurs de temps en temps. Vous ne plaidez pas pour un singleton, mais pour un simple vieux local. Vous voulez savoir un enregistreur est toujours disponible. C'est à ça que sert un mondial. Vous n'avez pas besoin de savoir qu'aucun autre enregistreur ne peut jamais être instancié , ce que fait un singleton. (essayez d'écrire des tests unitaires pour votre enregistreur - c'est beaucoup plus facile si vous pouvez le créer et le détruire au besoin, et ce n'est pas possible avec un singleton)
jalf
13

Les singletons vous permettent essentiellement d'avoir un état global complexe dans des langues qui autrement rendent difficile ou impossible d'avoir des variables globales complexes.

Java en particulier utilise des singletons en remplacement des variables globales, car tout doit être contenu dans une classe. Les variables globales les plus proches sont les variables statiques publiques, qui peuvent être utilisées comme si elles étaient globales avecimport static

C ++ possède des variables globales, mais l'ordre dans lequel les constructeurs de variables de classe globales sont invoquées n'est pas défini. En tant que tel, un singleton vous permet de différer la création d'une variable globale jusqu'à la première utilisation de cette variable.

Les langages tels que Python et Ruby utilisent très peu de singletons car vous pouvez utiliser à la place des variables globales dans un module.

Alors, quand est-il bon / mauvais d'utiliser un singleton? À peu près exactement quand il serait bon / mauvais d'utiliser une variable globale.

Eli Courtwright
la source
Quand une variable globale est-elle "bonne"? Parfois, ils sont la meilleure solution de contournement pour un problème, mais ils ne sont jamais «bons».
DevSolar
1
La variable globale est bonne lorsqu'elle est utilisée partout, et tout peut y avoir accès. Une implémentation d'une machine de turing à état unique peut utiliser un singleton.
Lee Louviere
J'aime la couche d'indirection dans cette réponse: "quand il serait bon / mauvais d'utiliser un global". DevSolar et Lee Louviere obtiennent tous les deux la valeur avec laquelle ils sont d'accord, même si en réponse, on ne pouvait pas savoir qui ferait un commentaire.
Praxeolitic
6

Modern C ++ Design by Alexandrescu a un singleton générique hérité du thread-safe.

Pour ma valeur 2p, je pense qu'il est important d'avoir des durées de vie définies pour vos singletons (quand il est absolument nécessaire de les utiliser). Je ne laisse normalement pas la get()fonction statique instancier quoi que ce soit, et laisse la configuration et la destruction à une section dédiée de l'application principale. Cela permet de mettre en évidence les dépendances entre singletons - mais, comme souligné ci-dessus, il est préférable de les éviter si possible.

tenpn
la source
6
  • Comment implémenter correctement un singleton

Il y a un problème que je n'ai jamais vu mentionné, quelque chose que j'ai rencontré lors d'un emploi précédent. Nous avions des singletons C ++ qui étaient partagés entre les DLL, et les mécanismes habituels pour garantir qu'une seule instance d'une classe ne fonctionnent tout simplement pas. Le problème est que chaque DLL obtient son propre ensemble de variables statiques, ainsi que l'EXE. Si votre fonction get_instance est en ligne ou fait partie d'une bibliothèque statique, chaque DLL se terminera avec sa propre copie du "singleton".

La solution consiste à s'assurer que le code singleton n'est défini que dans une DLL ou EXE, ou à créer un gestionnaire singleton avec ces propriétés pour répartir les instances.

Mark Ransom
la source
10
Yo dawg, j'ai entendu dire que vous aimiez les Singletons, alors j'ai fait un Singleton pour votre Singleton, afin que vous puissiez anti-pattern pendant que vous anti-pattern.
Eva
@Eva, ouais quelque chose comme ça. Je n'ai pas créé le problème, je devais juste le faire fonctionner d'une manière ou d'une autre.
Mark Ransom
5

Le premier exemple n'est pas sûr pour les threads - si deux threads appellent getInstance en même temps, cette statique va être un PITA. Une certaine forme de mutex aiderait.

Rob
la source
Oui, ce qui est noté dans les commentaires ci-dessus: * Limitation: conception à filetage unique * Voir: aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf * Pour les problèmes associés au verrouillage dans les applications multithread
Martin York
Le singleton classique avec uniquement getInstance comme méthode statique et les méthodes d'instance pour d'autres opérations ne peut jamais être sécurisé pour les threads. (enfin, à moins que vous n'en fassiez une tonne par thread en utilisant le stockage local de threads ...)
Tobi
même en c ++ 11 ou version ultérieure?
hg_git
5

Comme d'autres l'ont noté, les principaux inconvénients des singletons incluent l'incapacité de les étendre et la perte du pouvoir d'instancier plus d'une instance, par exemple à des fins de test.

Quelques aspects utiles des singletons:

  1. instanciation paresseuse ou initiale
  2. pratique pour un objet qui nécessite une configuration et / ou un état

Cependant, vous n'avez pas besoin d'utiliser un singleton pour obtenir ces avantages. Vous pouvez écrire un objet normal qui fait le travail, puis demander à des personnes d'y accéder via une usine (un objet séparé). L'usine peut se soucier de n'en instancier qu'une seule, de la réutiliser, etc., si besoin est. De plus, si vous programmez sur une interface plutôt que sur une classe concrète, l'usine peut utiliser des stratégies, c'est-à-dire que vous pouvez activer et désactiver diverses implémentations de l'interface.

Enfin, une usine se prête à des technologies d'injection de dépendance comme Spring, etc.

lexh
la source
3

Les singletons sont pratiques lorsque vous avez beaucoup de code en cours d'exécution lorsque vous initialisez et objectez. Par exemple, lorsque vous utilisez iBatis lorsque vous configurez un objet de persistance, il doit lire toutes les configurations, analyser les cartes, s'assurer que tout est correct, etc. avant d'accéder à votre code.

Si vous faisiez cela à chaque fois, les performances seraient considérablement dégradées. En l'utilisant dans un singleton, vous prenez ce coup une fois et tous les appels suivants n'ont pas à le faire.

Brian
la source
Le modèle de prototype le fait également, et il est plus flexible. Vous pouvez également l'utiliser lorsque votre client créera de nombreuses instances de votre classe coûteuse, mais seul un nombre limité d'entre elles ont en fait un état différent. Par exemple, les tétronimos dans Tetris.
Eva
3

La véritable chute des Singletons est qu'ils rompent l'héritage. Vous ne pouvez pas dériver une nouvelle classe pour vous offrir des fonctionnalités étendues à moins d'avoir accès au code où le Singleton est référencé. Ainsi, au-delà du fait que le Singleton rendra votre code étroitement couplé (réparable par un modèle de stratégie ... alias Dependency Injection), il vous empêchera également de fermer des sections du code de la révision (bibliothèques partagées).

Ainsi, même les exemples d'enregistreurs ou de pools de threads ne sont pas valides et doivent être remplacés par des stratégies.

ZebZiggle
la source
Les bûcherons eux-mêmes ne devraient pas être des singletons. Le système de messagerie général "broadcast" devrait être. Les enregistreurs eux-mêmes sont abonnés aux messages diffusés.
CashCow
Les pools de threads ne doivent pas non plus être des singletons. La question générale est: voudriez-vous jamais plus d'un d'entre eux? Oui. Lors de ma dernière utilisation, nous avions 3 pools de threads différents dans une seule application.
CashCow
3

La plupart des gens utilisent des singletons lorsqu'ils essaient de se sentir à l'aise avec l'utilisation d'une variable globale. Il existe des utilisations légitimes, mais la plupart du temps lorsque les gens les utilisent, le fait qu'il ne peut y avoir qu'une seule instance n'est qu'un fait trivial par rapport au fait qu'il est accessible à l'échelle mondiale.

Brad Barker
la source
3

Étant donné qu'un singleton ne permet de créer qu'une seule instance, il contrôle efficacement la réplication des instances. par exemple, vous n'auriez pas besoin de plusieurs instances d'une recherche - une carte de recherche morse par exemple, donc l'envelopper dans une classe singleton est approprié. Et ce n'est pas parce que vous avez une seule instance de la classe que vous êtes également limité sur le nombre de références à cette instance. Vous pouvez mettre les appels en file d'attente (pour éviter les problèmes de thread) sur l'instance et effectuer les modifications nécessaires. Oui, la forme générale d'un singleton est une forme publique mondiale, vous pouvez certainement modifier la conception pour créer un singleton à accès plus restreint. Je n'ai jamais fatigué cela auparavant mais je sais que c'est possible. Et pour tous ceux qui ont dit que le motif singleton est totalement diabolique, vous devez savoir ceci:

gogole
la source
2

Mais quand j'ai besoin de quelque chose comme un Singleton, je finis souvent par utiliser un compteur Schwarz pour l'instancier.

Matt Cruikshank
la source
2

Vous trouverez ci-dessous la meilleure approche pour implémenter un modèle singleton thread-safe avec désallocation de la mémoire dans le destructeur lui-même. Mais je pense que le destructeur devrait être facultatif car l'instance de singleton sera automatiquement détruite à la fin du programme:

#include<iostream>
#include<mutex>

using namespace std;
std::mutex mtx;

class MySingleton{
private:
    static MySingleton * singletonInstance;
    MySingleton();
    ~MySingleton();
public:
    static MySingleton* GetInstance();
    MySingleton(const MySingleton&) = delete;
    const MySingleton& operator=(const MySingleton&) = delete;
    MySingleton(MySingleton&& other) noexcept = delete;
    MySingleton& operator=(MySingleton&& other) noexcept = delete;
};

MySingleton* MySingleton::singletonInstance = nullptr;
MySingleton::MySingleton(){ };
MySingleton::~MySingleton(){
    delete singletonInstance;
};

MySingleton* MySingleton::GetInstance(){
    if (singletonInstance == NULL){
        std::lock_guard<std::mutex> lock(mtx);
        if (singletonInstance == NULL)
            singletonInstance = new MySingleton();
    }
    return singletonInstance;
}

Concernant les situations où nous devons utiliser des classes singleton peuvent être- Si nous voulons maintenir l'état de l'instance tout au long de l'exécution du programme Si nous sommes impliqués dans l'écriture dans le journal d'exécution d'une application où une seule instance du fichier doit être utilisé .... et ainsi de suite. Ce sera appréciable si quelqu'un peut suggérer une optimisation dans mon code ci-dessus.

A. Gupta
la source
2
Ce n'est certainement pas mieux. 1: Vous ne définissez pas la sémantique de propriété à l'aide du pointeur. Vous ne devez jamais utiliser de pointeurs en C ++ à moins que vous ne soyez prêt à les gérer. 2: Votre utilisation du verrouillage à double vérification est dépassée et il existe de bien meilleures façons modernes de le faire. 3: Vos commentaires sur la destruction sont naïfs. La récupération de la mémoire n'est pas le point d'utilisation du destructeur, il s'agit du nettoyage. Suggestions pour une meilleure version: regardez la question. La version qui y est présentée est déjà bien meilleure.
Martin York
1

J'utilise des singletons comme test d'entrevue.

Lorsque je demande à un développeur de nommer certains modèles de conception, s'ils ne peuvent nommer que Singleton, ils ne sont pas embauchés.

Matt Cruikshank
la source
45
Des règles strictes sur l'embauche vous feront manquer une grande diversité d'employés potentiels.
Karl
13
Il existe une grande diversité d'idiots. Cela ne signifie pas qu'ils devraient être pris en considération pour l'embauche. Si quelqu'un ne peut mentionner aucun motif de conception, je pense qu'il serait préférable à quelqu'un qui connaît le singleton et aucun autre motif.
jalf
3
Pour le livre des records - ma réponse a été ironique. Dans mon processus d'entrevue, j'essaie d'évaluer si nous aurons besoin de tutorat en C ++ et à quel point ce sera difficile. Certains de mes candidats préférés sont des gens qui ne connaissent PAS le C ++ à fond, mais j'ai pu avoir une excellente conversation avec eux à ce sujet.
Matt Cruikshank
4
Vote contre. D'après mon expérience personnelle - le programmeur peut ne pas être en mesure de nommer d'autres motifs que Singleton, mais cela ne signifie pas qu'il utilise des singletons. Personnellement, j'utilisais des singletons dans mon code AVANT d'en avoir entendu parler (je les appelais des "globaux plus intelligents" - je savais ce qu'est le global). Quand j'ai appris à leur sujet, quand j'ai appris leurs avantages et leurs inconvénients - j'ai cessé de les utiliser. Soudain, les tests unitaires sont devenus beaucoup plus intéressants pour moi quand j'ai arrêté ... Est-ce que cela fait de moi un pire programmeur? Pfff ...
Paulius
3
Je suis également en train de voter pour la question absurde "nommer certains modèles de conception". La conception consiste à comprendre comment appliquer des modèles de conception, et pas seulement à pouvoir dérober leurs noms. Ok, cela ne justifie peut-être pas un downvote mais cette réponse est troll-ish.
CashCow
0

Anti-utilisation:

Un problème majeur avec une utilisation excessive de singleton est que le modèle empêche une extension et un échange faciles d'implémentations alternatives. Le nom de classe est codé en dur partout où le singleton est utilisé.

Adam Franco
la source
Downvoted pour 2 raisons: 1. Singleton peut utiliser en interne des instances polymorphes (par exemple, Global Logger utilise des stratégies polymorphes de ciblage) 2. Il peut y avoir typedef pour le nom singleton, donc le code dépend en fait de typedef.
topright gamedev
J'ai fini par construire ma version d'un singleton pour être extensible en utilisant le modèle de modèle curieusement récurrent.
Zachary Kraus
0

Je pense que c'est la version la plus robuste pour C #:

using System;
using System.Collections;
using System.Threading;

namespace DoFactory.GangOfFour.Singleton.RealWorld
{

  // MainApp test application

  class MainApp
  {
    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Same instance?
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 server requests
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // "Singleton"

  class LoadBalancer
  {
    private static LoadBalancer instance;
    private ArrayList servers = new ArrayList();

    private Random random = new Random();

    // Lock synchronization object
    private static object syncLock = new object();

    // Constructor (protected)
    protected LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      // Support multithreaded applications through
      // 'Double checked locking' pattern which (once
      // the instance exists) avoids locking each
      // time the method is invoked
      if (instance == null)
      {
        lock (syncLock)
        {
          if (instance == null)
          {
            instance = new LoadBalancer();
          }
        }
      }

      return instance;
    }

    // Simple, but effective random load balancer

    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Voici la version optimisée .NET :

using System;
using System.Collections;

namespace DoFactory.GangOfFour.Singleton.NETOptimized
{

  // MainApp test application

  class MainApp
  {

    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Confirm these are the same instance
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 requests for a server
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // Singleton

  sealed class LoadBalancer
  {
    // Static members are lazily initialized.
    // .NET guarantees thread safety for static initialization
    private static readonly LoadBalancer instance =
      new LoadBalancer();

    private ArrayList servers = new ArrayList();
    private Random random = new Random();

    // Note: constructor is private.
    private LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      return instance;
    }

    // Simple, but effective load balancer
    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Vous pouvez trouver ce modèle sur dotfactory.com .

artur02
la source
3
Vous pouvez supprimer les parties qui ne sont pas spécifiquement pertinentes pour les singletons afin de faciliter la lecture du code.
Martin York
De plus, votre première version n'est pas adaptée aux threads en raison d'une réorganisation en lecture / écriture possible. Voir stackoverflow.com/questions/9666/…
Thomas Danecker
5
Euh ... mauvaise langue? La question est bien évidemment étiquetée C ++ .
DevSolar
0

Le motif Meyers singleton fonctionne assez bien la plupart du temps, et dans les occasions où il le fait, il n'est pas nécessairement payant de chercher quelque chose de mieux. Tant que le constructeur ne lancera jamais et qu'il n'y a pas de dépendances entre singletons.

Un singleton est une implémentation pour un objet accessible globalement (GAO à partir de maintenant) bien que tous les GAO ne soient pas des singletons.

Les enregistreurs eux-mêmes ne devraient pas être des singletons, mais les moyens de journalisation devraient idéalement être accessibles à l'échelle mondiale, pour découpler où le message de journal est généré d'où et comment il est enregistré.

L'évaluation paresseux / paresseux est un concept différent et singleton l'implémente généralement aussi. Il vient avec beaucoup de ses propres problèmes, en particulier la sécurité des threads et les problèmes s'il échoue avec des exceptions telles que ce qui semblait être une bonne idée à l'époque ne s'avère pas si bon après tout. (Un peu comme la mise en œuvre de COW dans les chaînes).

Dans cet esprit, les GOA peuvent être initialisés comme suit:

namespace {

T1 * pt1 = NULL;
T2 * pt2 = NULL;
T3 * pt3 = NULL;
T4 * pt4 = NULL;

}

int main( int argc, char* argv[])
{
   T1 t1(args1);
   T2 t2(args2);
   T3 t3(args3);
   T4 t4(args4);

   pt1 = &t1;
   pt2 = &t2;
   pt3 = &t3;
   pt4 = &t4;

   dostuff();

}

T1& getT1()
{
   return *pt1;
}

T2& getT2()
{
   return *pt2;
}

T3& getT3()
{
  return *pt3;
}

T4& getT4()
{
  return *pt4;
}

Cela n'a pas besoin d'être fait aussi grossièrement que cela, et clairement dans une bibliothèque chargée qui contient des objets, vous voulez probablement un autre mécanisme pour gérer leur durée de vie. (Mettez-les dans un objet que vous obtenez lorsque vous chargez la bibliothèque).

Quant à quand j'utilise des singletons? Je les ai utilisés pour 2 choses - Une table singleton qui indique à quelles bibliothèques ont été chargées dlopen - Un gestionnaire de messages auquel les enregistreurs peuvent s'abonner et auquel vous pouvez envoyer des messages. Requis spécifiquement pour les gestionnaires de signaux.

Vache à lait
la source
0

Je ne comprends toujours pas pourquoi un singleton doit être global.

J'allais produire un singleton où j'ai caché une base de données à l'intérieur de la classe en tant que variable statique constante privée et créer des fonctions de classe qui utilisent la base de données sans jamais exposer la base de données à l'utilisateur.

Je ne vois pas pourquoi cette fonctionnalité serait mauvaise.

Zachary Kraus
la source
Je ne comprends pas pourquoi vous pensez que ce doit être un monde.
Martin York
selon ce fil, Tout le monde disait qu'un singleton doit être mondial
Zachary Kraus
1
Non. Le fil indique qu'un singelton a un état global. Non pas que ce soit une variable globale. La solution que vous proposez a un état global. La solution que vous proposez utilise également une variable globale; un membre statique d'une classe est un objet de "Durée de stockage statique" une variable globale est un objet de "Durée de stockage statique". Ainsi, les deux sont fondamentalement la même chose avec des sémantiques / portées légèrement différentes.
Martin York
Une variable statique privée est donc toujours globale en raison de la "durée de stockage statique"?
Zachary Kraus
1
Remarque: Vous avez manqué mon bit délibérément non déclaré. Votre conception d'utiliser un membre statique "privé" n'est pas mauvaise de la même manière qu'un singelton. Parce qu'il n'introduit pas "d'état mutable global". Mais ce n'est pas non plus un singleton. Un singleton est une classe conçue de sorte qu'une seule instance de l'objet puisse exister. Ce que vous proposez, c'est un état partagé unique pour tous les objets d'une classe. Concept différent.
Martin York
0

Je les trouve utiles lorsque j'ai une classe qui encapsule beaucoup de mémoire. Par exemple, dans un jeu récent sur lequel j'ai travaillé, j'ai une classe de carte d'influence qui contient une collection de très grands tableaux de mémoire contiguë. Je veux que tout soit alloué au démarrage, tout libéré à l'arrêt et je ne veux certainement qu'une seule copie de celui-ci. Je dois également y accéder depuis de nombreux endroits. Je trouve que le motif singleton est très utile dans ce cas.

Je suis sûr qu'il existe d'autres solutions mais je trouve celle-ci très utile et facile à mettre en œuvre.

Michael Avraamides
la source
0

Si vous êtes celui qui a créé le singleton et qui l'utilise, ne le faites pas comme singleton (cela n'a pas de sens car vous pouvez contrôler la singularité de l'objet sans le rendre singleton) mais cela a du sens lorsque vous développez un et vous souhaitez fournir un seul objet à vos utilisateurs (dans ce cas, c'est vous qui avez créé le singleton, mais vous n'êtes pas l'utilisateur).

Les singletons sont des objets, alors utilisez-les comme objets, beaucoup de gens accèdent directement aux singletons en appelant la méthode qui le renvoie, mais cela est nuisible parce que vous faites savoir à votre code que l'objet est singleton, je préfère utiliser des singletons comme objets, je les passe à travers le constructeur et je les utilise comme des objets ordinaires, de cette façon, votre code ne sait pas si ces objets sont singletons ou non et cela rend les dépendances plus claires et cela aide un peu pour le refactoring ...

La VloZ Merrill
la source
-1

Dans les applications de bureau (je sais, seuls nous, les dinosaures, nous les écrivons plus!), Ils sont essentiels pour obtenir des paramètres d'application globaux relativement immuables - la langue de l'utilisateur, le chemin d'accès aux fichiers d'aide, les préférences de l'utilisateur, etc. qui autrement devraient se propager dans chaque classe et chaque boîte de dialogue .

Modifier - bien sûr, ceux-ci doivent être en lecture seule!

Martin Beckett
la source
Mais cela pose la question; pourquoi la langue de l' utilisateur et le chemin du fichier d'aide doivent être méthode des cas à tous ?
DrPizza
2
Nous avons des mondiaux pour cela. Il n'y a pas besoin de les faire singletons
jalf
Variables globales - alors comment les sérialiser à partir du registre / de la base de données? Classe Gobal - alors comment vous assurer qu'il n'y en a qu'un seul?
Martin Beckett
@mgb: vous les sérialisez en lisant les valeurs du registre / base de données et en les stockant dans les variables globales (cela devrait probablement être fait en haut de votre fonction principale). vous vous assurez qu'il n'y a qu'un seul objet d'une classe, en créant un seul objet de classe ... vraiment ... c'est si difficile de 'grep -rn "new \ + global_class_name".' ? vraiment?
Paulius
7
@mgb: Pourquoi diable pourrais-je m'assurer qu'il n'y en a qu'un? J'ai juste besoin de savoir qu'une instance représente toujours les paramètres actuels . mais il n'y a aucune raison pour laquelle je ne devrais pas être autorisé à avoir d'autres objets de paramètres autour de l'endroit. Peut-être un pour "les paramètres que l'utilisateur définit actuellement, mais n'a pas encore appliqués", par exemple. Ou un pour "la configuration que l'utilisateur a enregistrée plus tôt afin qu'il puisse y revenir plus tard". Ou un pour chacun de vos tests unitaires.
jalf
-1

Une autre implémentation

class Singleton
{
public:
    static Singleton& Instance()
    {
        // lazy initialize
        if (instance_ == NULL) instance_ = new Singleton();

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};
user1135926
la source
3
C'est vraiment horrible. Voir: stackoverflow.com/questions/1008019/c-singleton-design-pattern/… pour une meilleure initialisation paresseuse et plus important encore une destruction déterministe garantie.
Martin York
Si vous allez utiliser des pointeurs, vous devez Instance()renvoyer un pointeur, pas une référence. A l' intérieur de votre .cppfichier, initialiser l'instance null: Singleton* Singleton::instance_ = nullptr;. Et Instance()devrait être mis en œuvre: if (instance_ == nullptr) instance_ = new Singleton(); return instance_;.
Dennis