c++ - thread - singleton java




Modèle de conception Singleton C++ (12)

Récemment, je suis tombé sur une réalisation / mise en œuvre 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 flou pour moi, c'est quand exactement la mémoire va être désalloué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 question principale est, comment puis-je l'implémenter correctement?


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

Il sera détruit et libéré lorsque le programme se termine, ce qui est le comportement normal 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 le restaurer dans un état propre, et de le réaffecter la prochaine fois qu'il est utilisé, mais cela sort du cadre d'un Singleton "classique".


Ceci concerne la gestion de la vie de l'objet. 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 objet singleton utilise Logger pour consigner 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


Est-ce que quelqu'un a mentionné std::call_once et std::once_flag ? La plupart des autres approches, y compris le double verrouillage, sont brisées.

Un problème majeur dans l'implémentation 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 obstacles doivent eux-mêmes être initiés en toute sécurité. std::once_flag est le mécanisme permettant d'obtenir une initialisation sûre.


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 (tirée 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 qui sort du cadre pour 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 cela est que ce n'est pas thread-safe. Dans un environnement multithread, deux threads peuvent passer par le "si" avant que l'un ou l'autre n'ait une chance d'allouer la nouvelle instance (donc les deux le feraient). Ce n'est toujours pas un gros problème si vous comptez sur la fin du programme pour nettoyer quand même.


Je n'ai pas trouvé d'implémentation de CRTP parmi les réponses, alors 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>


Je pense que vous devriez écrire une fonction statique dans laquelle votre objet statique est supprimé. Vous devriez appeler cette fonction lorsque vous êtes sur le point de fermer votre application. Cela vous assurera que vous n'avez pas de fuite de mémoire.


Le papier 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 que le constructeur de l'objet a été appelé. Il est assez facile en c ++ d'utiliser allocaters pour allouer la mémoire manuellement, puis utiliser un appel de construction pour initialiser la mémoire. En utilisant cette approche, le verrouillage à double vérification fonctionne très bien.


Que diriez-vous d'utiliser l'emplacement 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;
    }
};

Une autre alternative sans allocation: créez un singleton, disons de classe C , comme vous en avez besoin:

singleton<C>()

en utilisant

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

Ni cela ni la réponse de Cătălin ne sont automatiquement thread-safe dans le C ++ actuel, mais seront en C ++ 0x.


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 renvoyé chaque fois après les mots.

SingletonClass instance created!
00915CB8
00915CB8

Ici 00915CB8 est l'emplacement de 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é.

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


Vous pourriez éviter l'allocation de mémoire. Il existe de nombreuses variantes, toutes ayant des problèmes en cas d'environnement multithread.

Je préfère ce genre d'implémentation (en fait, on ne dit pas correctement que 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 dynamique.


La réponse de @Loki Astari est excellente.

Cependant, il y a des moments où il y a plusieurs objets statiques où vous devez être capable de garantir que le singleton ne sera pas détruit tant que tous vos objets statiques qui utilisent le singleton n'en auront plus besoin.

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

private:
    Singleton() {}
};






singleton