it-swarm.com.de

C ++ Singleton-Entwurfsmuster

Kürzlich bin ich auf eine Realisierung/Implementierung des Singleton-Entwurfsmusters für C++ gestoßen. Es hat so ausgesehen (ich habe es aus dem realen Beispiel übernommen):

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

Aus dieser Deklaration kann ich ableiten, dass das Instanzfeld auf dem Heap initiiert wird. Das heißt, es gibt eine Speicherzuordnung. Was für mich völlig unklar ist, wann genau der Speicher freigegeben wird? Oder gibt es einen Fehler und ein Speicherleck? Anscheinend liegt ein Problem bei der Implementierung vor.

Meine Hauptfrage ist, wie ich es richtig umsetze.

667
Artem Barger

2008 stellte ich eine C++ 98-Implementierung des Singleton-Entwurfsmusters zur Verfügung, die faul bewertet, garantiert zerstört und technisch nicht threadsicher ist:
Kann mir jemand ein Beispiel für Singleton in c ++ geben?

Hier ist eine aktualisierte C++ 11-Implementierung des Singleton-Entwurfsmusters, die faul bewertet, korrekt zerstört und threadsicher ist.

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
};

In diesem Artikel erfahren Sie, wann ein Singleton zu verwenden ist: (nicht oft)
Singleton: Wie soll es verwendet werden

Lesen Sie diesen Artikel über die Reihenfolge der Initialisierung und wie Sie damit umgehen:
Reihenfolge der Initialisierung statischer Variablen
Probleme mit der statischen C++ - Initialisierungsreihenfolge

In diesem Artikel werden die Lebensdauern beschrieben:
Wie lang ist die Lebensdauer einer statischen Variablen in einer C++ - Funktion?

In diesem Artikel werden einige Threading-Auswirkungen auf Singletons erläutert:
Singleton-Instanz als statische Variable der GetInstance-Methode deklariert, ist sie threadsicher?

In diesem Artikel wird erklärt, warum doppelt überprüfte Sperren in C++ nicht funktionieren:
Was sind die häufigsten undefinierten Verhaltensweisen, die ein C++ - Programmierer kennen sollte?
Dr. Dobbs: C++ und die Gefahren des doppelten Sperrens: Teil I

1003
Martin York

Da Sie ein Singleton sind, möchten Sie normalerweise nicht, dass er zerstört wird.

Es wird abgerissen und freigegeben, wenn das Programm beendet wird. Dies ist das normale, gewünschte Verhalten für einen Singleton. Wenn Sie es explizit bereinigen möchten, ist es relativ einfach, der Klasse eine statische Methode hinzuzufügen, mit der Sie einen bereinigten Zustand wiederherstellen und sie bei der nächsten Verwendung neu zuweisen können. Dies liegt jedoch außerhalb des Bereichs von a "klassischer" Singleton.

46
Reed Copsey

Sie könnten die Speicherzuordnung vermeiden. Es gibt viele Varianten, die alle Probleme mit Multithreading-Umgebungen haben.

Ich bevorzuge diese Art der Implementierung (eigentlich ist es nicht richtig gesagt, dass ich es bevorzuge, weil ich Singletons so weit wie möglich vermeide):

class Singleton
{
private:
   Singleton();

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

Es gibt keine dynamische Speicherzuordnung.

34

@ Loki Astaris Antwort ist ausgezeichnet.

Es gibt jedoch Zeiten mit mehreren statischen Objekten, in denen Sie sicherstellen müssen, dass der Singleton erst dann zerstört wird, wenn alle statischen Objekte, die den Singleton braucht es nicht mehr.

In diesem Fall kann std::shared_ptr verwendet werden, um den Singleton für alle Benutzer am Leben zu halten, selbst wenn die statischen Destruktoren am Ende des Befehls aufgerufen werden Programm:

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() {}
};
12
Galik

Eine weitere nicht allokierende Alternative: Erstellen Sie einen Singleton, beispielsweise der Klasse C, wie Sie ihn benötigen:

singleton<C>()

mit

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

Weder diese noch Cătălins Antwort ist in aktuellem C++ automatisch threadsicher, wird aber in C++ 0x sein.

9
James Hopkin

Die Lösung in der akzeptierten Antwort hat einen erheblichen Nachteil: Der Destruktor für den Singleton wird aufgerufen, nachdem das Steuerelement die Funktion main() verlassen hat. Es kann wirklich Probleme geben, wenn einige abhängige Objekte in main zugewiesen werden.

Ich bin auf dieses Problem gestoßen, als ich versuchte, einen Singleton in die Qt-Anwendung einzuführen. Ich entschied, dass alle meine Setup-Dialoge Singletons sein müssen und übernahm das obige Muster. Leider wurde die Hauptklasse QApplication von Qt in der Funktion main auf dem Stack zugewiesen, und Qt verbietet das Erstellen/Zerstören von Dialogen, wenn kein Anwendungsobjekt verfügbar ist.

Deshalb bevorzuge ich haufenweise zugewiesene Singletons. Ich biete eine explizite init() und term() Methode für alle Singletons an und rufe sie in main auf. Somit habe ich die volle Kontrolle über die Reihenfolge der Erzeugung/Zerstörung von Singletons und garantiere auch, dass Singletons erzeugt werden, egal ob jemand getInstance() gerufen hat oder nicht.

6
SadSido

Wenn Sie das Objekt im Heap zuordnen möchten, verwenden Sie keinen eindeutigen Zeiger. Der Speicher wird ebenfalls freigegeben, da wir einen eindeutigen Zeiger verwenden.

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
riderchap

Ich habe in den Antworten keine CRTP-Implementierung gefunden, daher hier:

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;
    }
};

Um dies zu verwenden, erben Sie einfach Ihre Klasse wie folgt: class Test : public Singleton<Test>

4
Yuriy

Hier ist eine einfache Implementierung.

#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;
}

Es wird immer nur ein Objekt erstellt und diese Objektreferenz wird jedes Mal nachträglich zurückgegeben.

SingletonClass instance created!
00915CB8
00915CB8

Hier ist 00915CB8 der Speicherort von Singleton Object, der für die Dauer des Programms gleich ist, aber (normalerweise!) Bei jedem Programmlauf unterschiedlich ist.

N.B. Dies ist kein Thread-Safe. Sie müssen die Thread-Sicherheit sicherstellen.

Es wird zwar wahrscheinlich aus dem Haufen zugeteilt, aber ohne die Quellen gibt es keine Möglichkeit zu wissen.

Die typische Implementierung (entnommen aus einem Code, den ich bereits in Emacs habe) wäre:

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

... und verlassen Sie sich darauf, dass das Programm den Rahmen verlässt, um danach aufzuräumen.

Wenn Sie auf einer Plattform arbeiten, auf der die Bereinigung manuell erfolgen muss, würde ich wahrscheinlich eine manuelle Bereinigungsroutine hinzufügen.

Ein weiteres Problem dabei ist, dass es nicht threadsicher ist. In einer Multithread-Umgebung könnten zwei Threads das "Wenn" durchlaufen, bevor einer die Chance hat, die neue Instanz zuzuweisen (so würden beide). Dies ist immer noch keine allzu große Sache, wenn Sie sich auf die Programmbeendigung verlassen, um trotzdem aufzuräumen.

2
T.E.D.

Hat jemand std::call_once und std::once_flag erwähnt? Die meisten anderen Ansätze - einschließlich doppelt überprüfter Sperren - sind fehlerhaft.

Ein Hauptproblem bei der Implementierung von Singleton-Mustern ist die sichere Initialisierung. Die einzige sichere Möglichkeit besteht darin, die Initialisierungssequenz mit Synchronisierungsbarrieren zu schützen. Aber diese Barrieren müssen selbst sicher eingeleitet werden. std::once_flag ist der Mechanismus, mit dem eine sichere Initialisierung gewährleistet wird.

2
Red.Wave

Zusätzlich zu der anderen Diskussion hier kann es erwähnenswert sein, dass Sie Globalität haben können, ohne die Verwendung auf eine Instanz zu beschränken. Betrachten Sie zum Beispiel den Fall des Referenzzählens ...

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

Jetzt können Sie innerhalb einer Funktion (wie main) Folgendes tun:

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

Die Refs müssen keinen Zeiger zurück auf ihr jeweiliges Store speichern, da diese Informationen zur Kompilierungszeit bereitgestellt werden. Sie müssen sich auch keine Gedanken über die Lebensdauer von Store machen, da der Compiler voraussetzt, dass es global ist. Wenn es in der Tat nur eine Instanz von Store gibt, ist dieser Ansatz ohne zusätzlichen Aufwand. Bei mehr als einer Instanz ist es Sache des Compilers, bei der Codegenerierung klug zu sein. Bei Bedarf kann die Klasse ItemRef sogar zu einer friend von Store gemacht werden (Sie können Freunde mit Vorlagen haben!).

Wenn Store selbst eine Klasse mit Vorlagen ist, wird es schwieriger, aber es ist dennoch möglich, diese Methode zu verwenden, indem Sie möglicherweise eine Hilfsklasse mit der folgenden Signatur implementieren:

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>. */ };

Der Benutzer kann jetzt einen StoreWrapper-Typ (und eine globale Instanz) für jede globale Store -Instanz erstellen und immer über ihre Wrapper-Instanz auf die Stores zugreifen (und dabei die wichtigsten Details der für die Verwendung erforderlichen Vorlagenparameter vergessen Store).

1
dan-man

Hier geht es um die Verwaltung der Objektlebensdauer. Angenommen, Ihre Software enthält mehr als nur Singletons. Und sie hängen von Logger Singleton ab. Angenommen, ein anderes Singleton-Objekt verwendet während der Anwendungszerstörung Logger, um seine Zerstörungsschritte zu protokollieren. Sie müssen sicherstellen, dass der Logger zuletzt bereinigt wird. Lesen Sie daher auch dieses Dokument: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf

0
baris.aydinoz