C++ Singleton design pattern

récemment, je suis tombé sur une réalisation/implémentation du modèle de conception Singleton pour C++. Il a ressemblé à ceci (je l'ai adopté 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 est complètement obscur pour moi, c'est quand exactement la mémoire va être délocalisée? Ou y a-t-il un bug et une fuite de mémoire? Il semble qu'il y est un problème dans la mise en œuvre.

ma question principale est, Comment puis-je le mettre en œuvre de la bonne manière?

562
demandé sur FruitBreak 2009-06-17 20:02:22

18 réponses

en 2008 j'ai fourni une implémentation C++98 du modèle de conception Singleton qui est paresseux-évalué, garanti-destruction, pas-techniquement-sûr:

est-ce que quelqu'un peut me fournir un échantillon de Singleton en c++?

Voici une mise à jour de l'implémentation C++11 du modèle de conception Singleton qui est paresseux-évalué, correctement-détruit, et thread-safe .

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 compilers behavior to check accessibility
        //       before deleted status
};

voir cet article sur le moment d'utiliser un singleton: (pas souvent)

Singleton: comment doit-on l'utiliser

voir cet article sur l'ordre d'initialisation et comment faire face:

ordre D'initialisation des variables statiques

Finding C++ static initialization order problems

voir cet article décrivant vies:

Quelle est la durée de vie d'une variable statique dans une fonction C++?

voir cet article qui traite de certaines implications de threading pour Singleton:

instance Singleton déclarée comme variable statique de la méthode GetInstance, est-il thread-safe?

voir cet article qui explique pourquoi le verrouillage double vérifié ne fonctionnera pas sur C++:

Quels sont tous les comportements courants non définis qu'un programmeur C++ devrait connaître?

Dr Dobbs: C++ et les périls du double contrôle de verrouillage: partie i

867
répondu Martin York 2018-09-04 12:04:35

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

il va être démoli et désalloué quand le programme se termine, ce qui est le comportement normal et désiré pour un singleton. Si vous voulez être en mesure explicitement le nettoyer, il est assez facile d'ajouter une méthode statique de la classe qui permet de le restaurer à un état de propreté, et de réaffecter la prochaine fois qu'il l'utilise, mais c'est en dehors de la portée d'un "classique" singleton.

37
répondu Reed Copsey 2009-06-17 16:06:12

vous pourriez éviter l'attribution de mémoire. Il existe de nombreuses variantes, toutes ayant des problèmes en cas de multithreading environnement.

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

class Singleton
{
private:
   Singleton();

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

il n'a pas d'allocation de mémoire dynamique.

28
répondu Cătălin Pitiș 2009-06-17 16:10:55

une autre alternative non-attributive: créer un singleton, disons de la classe C , comme vous en avez besoin:

singleton<C>()

utilisant

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

ni cette réponse ni celle de Cătălin ne sont automatiquement sécurisées en C++ actuel, mais seront en C++0x.

10
répondu James Hopkin 2009-06-17 16:15:51

la réponse de @Loki Astari 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 ont plus besoin.

dans ce cas std::shared_ptr peut être utilisé pour garder le singleton vivant 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<Singleton> instance()
    {
        static std::shared_ptr<Singleton> s{new Singleton};
        return s;
    }

private:
    Singleton() {}
};
9
répondu Galik 2017-05-23 12:34:47

si vous voulez affecter l'objet en tas, pourquoi ne pas utiliser un pointeur unique. La mémoire sera également désactivé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<S> m_s;

        S();
        S(S const&);            // Don't Implement
        void operator=(S const&); // Don't implement
};

std::unique_ptr<S> S::m_s(0);
5
répondu riderchap 2017-03-28 13:20:35

la solution dans la réponse acceptée a un inconvénient important - le destructeur pour le singleton est appelé après le contrôle quitte la fonction main() . Il peut y avoir des problèmes en réalité, lorsque certains objets dépendants sont alloué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 dialogues de configuration devaient être des Singletons, et j'ai adopté le modèle ci-dessus. Malheureusement, la classe principale de Qt QApplication a été attribué sur la pile dans la fonction main , et Qt interdit de créer/détruire des boîtes de dialogue quand aucun objet d'application n'est disponible.

C'est pour cela que je préfère les singletons en tas. Je fournis une méthode explicite init() et term() pour tous les Singleton et les appelle à l'intérieur de main . Ainsi j'ai un contrôle total sur l'ordre de création/destruction des singletons, et aussi je garantis que les singletons seront créés, peu importe si quelqu'un appelé getInstance() ou pas.

5
répondu SadSido 2018-10-01 23:17:44

Voici une implémentation facile.

#include <Windows.h>
#include <iostream>

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 objet créé et cette référence d'objet est retournée à chaque fois après mots.

SingletonClass instance created!
00915CB8
00915CB8

ici 00915CB8 est l'emplacement mémoire de l'objet singleton, même pour la durée du programme mais (normalement!) différent à chaque fois que le programme est exécuté.

N.B. ce n'est pas un filet sûr.Vous devez assurer la sécurité du fil.

4
répondu Tunvir Rahman Tusher 2016-11-17 20:17:04

il est en effet probablement attribué à partir du tas, mais sans les sources, il n'y a aucun moyen de savoir.

l'implémentation typique (prise à partir d'un code que j'ai déjà dans emacs) serait:

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

...et compter sur le programme de sortir de la portée à nettoyer après.

si vous travaillez sur une plate-forme où le nettoyage doit être fait manuellement, j'ajouterais probablement une routine de nettoyage manuel.

Un autre problème avec le faire de cette façon est qu'il n'est pas thread-safe. Dans un environnement multithread, deux threads peuvent passer à travers le "si" avant que l'un ou l'autre ait une chance d'allouer la nouvelle instance (donc les deux le feront). Ce n'est pas trop grave si vous comptez sur la fin du programme pour nettoyer de toute façon.

2
répondu T.E.D. 2009-06-17 16:14:23

il s'agit de la gestion de la durée de vie de l'objet. 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 enregistrer ses étapes de destruction. Vous devez garantir que Logger doit être nettoyé en dernier. Par conséquent, veuillez également consulter ce document: http://www.cs.wustl.edu / ~ schmidt/PDF / ObjMan.pdf

1
répondu baris.aydinoz 2010-04-01 08:42:05

Je n'ai pas trouvé d'implémentation CRTP parmi les réponses, donc voici:

template<typename HeirT>
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 votre classe de ceci, comme: class Test : public Singleton<Test>

1
répondu Yuriy 2018-03-14 12:15:03

Quelqu'un a-t-il mentionné std::call_once et std::once_flag ? La plupart des autres approches - y compris le verrouillage à double contrôle - sont brisées.

un problème majeur dans la mise en œuvre du modèle singleton est l'initialisation sûre. Le seul moyen sûr est de garder la séquence d'initialisation avec des barrières de synchronisation. Mais ces barrières elles-mêmes doivent être mises en place en toute sécurité. std::once_flag est le mécanisme pour obtenir l'initialisation sûre garantie.

1
répondu Red.Wave 2018-04-10 13:14:32
#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);
0
répondu Gank 2014-01-18 19:05:14

en plus de l'autre discussion ici, il peut être intéressant de noter que vous pouvez avoir global-ness, sans limiter l'usage à une instance. Par exemple, considérons le cas d'une référence comptant quelque chose...

struct Store{
   std::array<Something, 1024> data;
   size_t get(size_t idx){ /* ... */ }
   void incr_ref(size_t idx){ /* ... */}
   void decr_ref(size_t idx){ /* ... */}
};

template<Store* store_p>
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 restrict the number of global Store instances

maintenant quelque part à l'intérieur d'une fonction (comme 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 de retour à leur Store respectif parce que cette information est fournie à au moment de la compilation. Vous n'avez pas non plus à vous soucier de la durée de vie du Store parce que le compilateur exige qu'il soit global. S'il n'y a en effet qu'une seule instance de Store , alors 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, la classe ItemRef peut même être faite une friend de Store (vous pouvez avoir des amis templated!).

si Store lui-même est un classe templated puis les choses deviennent plus désordonnées, mais il est encore possible d'utiliser cette méthode, peut-être en implémentant une classe helper avec la signature suivante:

template <typename Store_t, Store_t* store_p>
struct StoreWrapper{ /* stuff to access store_p, e.g. methods returning 
                       instances of ItemRef<Store_t, store_p>. */ };

l'utilisateur peut maintenant créer un type StoreWrapper (et une instance globale) pour chaque instance globale Store , et accéder toujours aux magasins via leur instance wrapper (oubliant ainsi les détails sanglants des paramètres du modèle nécessaires pour utiliser Store ).

0
répondu dan-man 2015-11-09 11:24:30

classe simple singleton, ce doit être votre fichier de classe d'en-tête

#ifndef SC_SINGLETON_CLASS_H
#define SC_SINGLETON_CLASS_H

class SingletonClass
{
    public:
        static SingletonClass* Instance()
        {
           static SingletonClass* instance = new SingletonClass();
           return instance;
        }

        void Relocate(int X, int Y, int Z);

    private:
        SingletonClass();
        ~SingletonClass();
};

#define sSingletonClass SingletonClass::Instance()

#endif

accédez à votre singleton comme ceci:

sSingletonClass->Relocate(1, 2, 5);
0
répondu Ali Khazaee 2018-09-11 07:23:23

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 vous assurera de ne pas avoir de fuite de mémoire.

-1
répondu Yogi 2011-08-18 11:38:22

le papier qui a été lié à ci-dessus décrit le défaut de double verrouillage Vérifié est que le compilateur peut allouer la mémoire pour l'objet et mettre un pointeur à l'adresse de la mémoire allouée, avant que le constructeur de l'objet a été appelé. Il est assez facile en c++ cependant d'utiliser allocaters pour allouer la mémoire manuellement, puis d'utiliser un appel de construction pour initialiser la mémoire. En utilisant cette apprêt, le double-vérifié de verrouillage fonctionne très bien.

-1
répondu sdfsdaf 2011-10-17 22:06:18

Que Diriez-vous d'utiliser le placement nouveau 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;
    }
};
-5
répondu rahul 2012-02-24 15:59:00