C ++ Singleton-Entwurfsmuster


736

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 schließen, dass das Instanzfeld auf dem Heap initiiert ist. Das heißt, es gibt eine Speicherzuordnung. Was für mich völlig unklar ist, ist, wann genau die Erinnerung freigegeben wird? Oder gibt es einen Fehler und einen Speicherverlust? Es scheint, dass es ein Problem bei der Implementierung gibt.

Meine Hauptfrage ist, wie ich es richtig umsetze.



10
In diesem Dokument finden Sie eine ausführliche Beschreibung der Implementierung eines Singletons sowie der Thread-Sicherheit in C ++. aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf

106
@sbi - Nur ein Sith handelt absolut. Kann die überwiegende Mehrheit der Probleme ohne Singletons gelöst werden? Absolut. Verursachen Singletons eigene Probleme? Ja. Ich kann jedoch nicht ehrlich sagen, dass sie schlecht sind , da es beim Design darum geht, die Kompromisse zu berücksichtigen und die Nuancen Ihres Ansatzes zu verstehen.
Derekkmann

11
@derekerdmann: Ich habe nicht gesagt, dass Sie niemals eine globale Variable benötigen (und wenn Sie eine benötigen, ist ein Singleton manchmal besser). Was ich gesagt habe ist, dass sie so wenig wie möglich verwendet werden sollten. Die Verherrlichung von Singleton als wertvolles Designmuster vermittelt den Eindruck, dass es gut ist, es zu verwenden, anstatt dass es ein Hack ist , der Code schwer verständlich, schwer zu warten und schwer zu testen macht. Deshalb habe ich meinen Kommentar gepostet. Nichts von dem, was Sie bisher gesagt haben, widersprach dem.
sbi

13
@sbi: Was du gesagt hast war "Benutze sie nicht." Nicht viel vernünftiger "sie sollten so wenig wie möglich verwendet werden", zu dem Sie später gewechselt haben - sicherlich sehen Sie den Unterschied.
JWD

Antworten:


1106

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

Hier ist eine aktualisierte C ++ 11-Implementierung des Singleton-Entwurfsmusters, die faul ausgewertet, 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 Sie einen Singleton verwenden müssen: (nicht oft)
Singleton: Wie sollte er verwendet werden?

In diesem Artikel finden Sie Informationen zur Initialisierungsreihenfolge und zum Umgang mit:
Initialisierungsreihenfolge statischer Variablen
Auffinden von Problemen mit der statischen Initialisierungsreihenfolge von C ++

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

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

In diesem Artikel wird erläutert, warum das doppelt überprüfte Sperren unter C ++ nicht funktioniert:
Welche allgemeinen undefinierten Verhaltensweisen sollte ein C ++ - Programmierer kennen?
Dr. Dobbs: C ++ und die Gefahren der doppelten Verriegelung: Teil I.


23
Gute Antwort. Aber sollte beachten, dass dies nicht threadsicher ist stackoverflow.com/questions/1661529/…
Varuna

4
@ Zourtney: Viele Leute wissen nicht, was Sie gerade getan haben :)
Johann Gerell

4
@ MaximYegorushkin: Wann dies zerstört wird, ist sehr gut definiert (es gibt keine Mehrdeutigkeit). Siehe: stackoverflow.com/questions/246564/…
Martin York

3
What irks me most though is the run-time check of the hidden boolean in getInstance()Dies ist eine Annahme zur Implementierungstechnik. Es muss keine Vermutungen darüber geben, dass es lebt. Siehe stackoverflow.com/a/335746/14065 Sie können eine Situation so erzwingen, dass sie immer aktiv ist (weniger Overhead als Schwarz counter). Globale Variablen haben mehr Probleme mit der Initialisierungsreihenfolge (über Kompilierungseinheiten hinweg), da Sie keine Reihenfolge erzwingen. Der Vorteil dieses Modells ist 1) verzögerte Initialisierung. 2) Fähigkeit, eine Anordnung durchzusetzen (Schwarz hilft, ist aber hässlicher). Ja get_instance()ist viel hässlicher.
Martin York

3
@kol: Nein. Es ist nicht das übliche. Nur weil Anfänger Code kopieren und einfügen, ohne darüber nachzudenken, ist er nicht der übliche. Sie sollten sich immer den Anwendungsfall ansehen und sicherstellen, dass der Zuweisungsoperator die erwarteten Aktionen ausführt. Das Kopieren und Einfügen von Code führt zu Fehlern.
Martin York

47

Als Singleton möchten Sie normalerweise nicht, dass es 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 ziemlich einfach, der Klasse eine statische Methode hinzuzufügen, mit der Sie sie in einen sauberen Zustand zurückversetzen und bei der nächsten Verwendung neu zuweisen können. Dies liegt jedoch außerhalb des Bereichs von a "klassischer" Singleton.


4
Wenn delete in der statischen Singleton * -Instanz niemals explizit aufgerufen wird, wird dies dann technisch immer noch nicht als Speicherverlust angesehen?
Andrew Garrison

7
Es ist kein Speicherverlust mehr als eine einfache Deklaration einer globalen Variablen.
ilya n.

15
Um etwas klar zu stellen ... "Memory Leak" betrifft Singletons völlig irrelevant. Wenn Sie über staatliche Ressourcen verfügen, bei denen es um die Dekonstruktionsreihenfolge geht, können Singletons gefährlich sein. Der gesamte Speicher wird jedoch beim Beenden des Programms vom Betriebssystem sauber wiederhergestellt. In 99,9% der Fälle wird dieser völlig akademische Punkt aufgehoben. Wenn Sie die Grammatik hin und her darüber streiten möchten, was ein "Speicherverlust" ist und was nicht, ist das in Ordnung, aber Sie müssen erkennen, dass dies eine Ablenkung von tatsächlichen Entwurfsentscheidungen ist.
Jkerian

12
@jkerian: Bei Speicherlecks und Zerstörung im C ++ - Kontext geht es nicht wirklich um Speicherlecks. Es geht wirklich um Ressourcenkontrolle. Wenn Sie Speicher verlieren, wird der Zerstörer nicht aufgerufen und daher werden alle mit dem Objekt verknüpften Ressourcen nicht korrekt freigegeben. Speicher ist nur das einfache Beispiel, das wir beim Unterrichten von Programmierung verwenden, aber es gibt viel komplexere Ressourcen.
Martin York

7
@ Martin Ich stimme dir vollkommen zu. Selbst wenn die einzige Ressource der Speicher ist, werden Sie immer noch Probleme haben, echte Lecks in Ihrem Programm zu finden, wenn Sie eine Liste von Lecks durchsuchen müssen, um diejenigen herauszufiltern, die "keine Rolle spielen". Es ist besser, diese alle zu bereinigen, damit jedes Tool, das Lecks meldet, nur Dinge meldet, die ein Problem darstellen.
Dolphin

38

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

Ich bevorzuge diese Art der Implementierung (eigentlich wird 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.


3
In einigen Fällen ist diese verzögerte Initialisierung nicht das ideale Muster. Ein Beispiel ist, wenn der Konstruktor des Singleton Speicher aus dem Heap zuweist und Sie möchten, dass die Zuordnung vorhersehbar ist, beispielsweise in einem eingebetteten System oder einer anderen streng kontrollierten Umgebung. Ich bevorzuge es, wenn das Singleton-Muster das beste Muster ist, die Instanz als statisches Mitglied der Klasse zu erstellen.
dma

3
Für viele größere Programme, insbesondere solche mit dynamischen Bibliotheken. Jedes globale oder statische Objekt, das nicht primitiv ist, kann beim Beenden des Programms auf vielen Plattformen zu Segfaults / Abstürzen führen, da beim Entladen der Bibliotheken Probleme mit der Reihenfolge der Zerstörung auftreten. Dies ist einer der Gründe, warum viele Codierungskonventionen (einschließlich der von Google) die Verwendung nicht trivialer statischer und globaler Objekte verbieten.
Obecalp

Es scheint, dass die statische Instanz in einer solchen Implementierung eine interne Verknüpfung aufweist und eindeutige und unabhängige Kopien in verschiedenen Übersetzungseinheiten aufweist, was zu verwirrendem und falschem Verhalten führt. Aber ich habe viele solcher Implementierungen gesehen. Vermisse ich etwas?
FaceBro

Was hindert den Benutzer daran, dies mehreren Objekten zuzuweisen, bei denen der Compiler hinter den Kulissen einen eigenen Kopierkonstruktor verwendet?
Tony Tannous

19

@Loki Astaris Antwort ist ausgezeichnet.

Es gibt jedoch Zeiten mit mehreren statischen Objekten, in denen Sie sicherstellen müssen, dass der Singleton nicht zerstört wird, bis alle statischen Objekte, die den Singleton verwenden, ihn nicht mehr benötigen.

In diesem Fall std::shared_ptrkann der Singleton für alle Benutzer am Leben erhalten werden, auch wenn die statischen Destruktoren am Ende des Programms aufgerufen werden:

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

Eine weitere nicht zuweisende Alternative: Erstellen Sie einen Singleton, z. B. eine Klasse C, nach Bedarf:

singleton<C>()

mit

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

Weder diese noch die Antwort von Cătălin sind in aktuellem C ++ automatisch threadsicher, sondern in C ++ 0x.


Derzeit ist es unter gcc threadsicher (und das schon eine Weile).
Martin York

13
Das Problem bei diesem Design besteht darin, dass es in mehreren Bibliotheken verwendet wird. Jede Bibliothek verfügt über eine eigene Kopie des von dieser Bibliothek verwendeten Singletons. Es ist also kein Singleton mehr.
Martin York

6

Ich habe unter 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 zu verwenden, erben Sie einfach Ihre Klasse davon, wie: class Test : public Singleton<Test>


1
Dies konnte nicht mit C ++ 17 funktionieren, bis ich den Standardkonstruktor geschützt und '= default;' gemacht habe.
WFranczyk

6

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

Ich bin auf dieses Problem gestoßen, als ich versucht habe, 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 von Qt QApplicationin der mainFunktion auf einem Stapel zugewiesen , und Qt verbietet das Erstellen / Zerstören von Dialogen, wenn kein Anwendungsobjekt verfügbar ist.

Deshalb bevorzuge ich Singletons mit Heap-Zuweisung. Ich biete eine explizite init()und term()Methoden für alle Singletons und rufe sie in main. Somit habe ich die volle Kontrolle über die Reihenfolge der Erstellung / Zerstörung von Singletons und garantiere auch, dass Singletons erstellt werden, unabhängig davon, ob jemand angerufen hat getInstance()oder nicht.


2
Wenn Sie sich auf die aktuell akzeptierte Antwort beziehen, ist Ihre erste Aussage falsch. Der Destruktor wird erst aufgerufen, wenn alle Objekte mit statischer Speicherdauer zerstört wurden.
Martin York

5

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 nur ein Objekt erstellt und diese Objektreferenz wird jedes Mal nach den Nachwörtern zurückgegeben.

SingletonClass instance created!
00915CB8
00915CB8

Hier ist 00915CB8 der Speicherort des Singleton-Objekts, der für die Dauer des Programms gleich ist, aber (normalerweise!) Jedes Mal anders ist, wenn das Programm ausgeführt wird.

Hinweis: Dies ist kein Thread-sicher. Sie müssen die Thread-Sicherheit gewährleisten.


5

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

3
In c ++ 11 veraltet. Stattdessen wird unique_ptr empfohlen. cplusplus.com/reference/memory/auto_ptr cplusplus.com/reference/memory/unique_ptr
Andrew

2
Dies ist nicht threadsicher. Es ist besser, m_sein Lokal staticzu getInstance()erstellen und es sofort ohne Test zu initialisieren.
Galik

2

Es wird zwar wahrscheinlich vom Haufen zugewiesen, 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 Umfang verlässt, um danach aufzuräumen.

Wenn Sie auf einer Plattform arbeiten, auf der die Bereinigung manuell durchgeführt werden 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 (also beide). Dies ist immer noch keine allzu große Sache, wenn Sie sich ohnehin auf die Programmbeendigung verlassen, um aufzuräumen.


Sie können ableiten, da Sie sehen, dass die Instanzvariable ein Zeiger auf die Klasseninstanz ist.
Artem Barger

3
Es ist nicht erforderlich, den Singleton dynamisch zuzuweisen. Tatsächlich ist dies eine schlechte Idee, da es keine Möglichkeit gibt, die Zuordnung mithilfe des obigen Designs automatisch aufzuheben. Lassen Sie es aus dem Rahmen fallen, nennt keine Destruktoren und ist nur faul.
Martin York

Sie können die Zuordnung mithilfe der Atexit-Funktion automatisch aufheben. Das ist, was wir tun (ohne zu sagen, dass es eine gute Idee ist)
Joe

2

Hat jemand erwähnt std::call_onceund std::once_flag? Die meisten anderen Ansätze - einschließlich der doppelt überprüften Verriegelung - sind fehlerhaft.

Ein Hauptproblem bei der Implementierung von Singleton-Mustern ist die sichere Initialisierung. Der einzig sichere Weg besteht darin, die Initialisierungssequenz mit Synchronisationsbarrieren zu schützen. Aber diese Barrieren selbst müssen sicher initiiert werden. std::once_flagist der Mechanismus, um eine sichere Initialisierung zu gewährleisten.


2

Wir haben dieses Thema kürzlich in meiner EECS-Klasse behandelt. Wenn Sie sich die Vorlesungsunterlagen ausführlich ansehen möchten, besuchen Sie http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf

Ich kenne zwei Möglichkeiten, um eine Singleton-Klasse korrekt zu erstellen.

Erster Weg:

Implementieren Sie es ähnlich wie in Ihrem Beispiel. Was die Zerstörung betrifft, "halten Singletons normalerweise die Dauer des Programmlaufs aus; die meisten Betriebssysteme stellen Speicher und die meisten anderen Ressourcen wieder her, wenn ein Programm beendet wird. Es gibt also ein Argument, sich darüber keine Sorgen zu machen."

Es wird jedoch empfohlen, bei Programmbeendigung aufzuräumen. Daher können Sie dies mit einer statischen SingletonDestructor-Hilfsklasse tun und dies als Freund in Ihrem Singleton deklarieren.

class Singleton {
public:
  static Singleton* get_instance();

  // disable copy/move -- this is a Singleton
  Singleton(const Singleton&) = delete;
  Singleton(Singleton&&) = delete;
  Singleton& operator=(const Singleton&) = delete;
  Singleton& operator=(Singleton&&) = delete;

  friend class Singleton_destroyer;

private:
  Singleton();  // no one else can create one
  ~Singleton(); // prevent accidental deletion

  static Singleton* ptr;
};

// auxiliary static object for destroying the memory of Singleton
class Singleton_destroyer {
public:
  ~Singleton_destroyer { delete Singleton::ptr; }
};

Der Singleton_destroyer wird beim Programmstart erstellt. "Wenn das Programm beendet wird, werden alle globalen / statischen Objekte durch den vom Linker eingefügten Code zum Herunterfahren der Laufzeitbibliothek zerstört, sodass der_Destroyer zerstört wird. Sein Destruktor löscht den Singleton und führt seinen aus Zerstörer. "

Zweiter Weg

Dies wird als Meyers Singleton bezeichnet und vom C ++ - Assistenten Scott Meyers erstellt. Definieren Sie get_instance () einfach anders. Jetzt können Sie auch die Zeigerelementvariable entfernen.

// public member function
static Singleton& Singleton::get_instance()
{
  static Singleton s;
  return s;
}

Dies ist ordentlich, da der zurückgegebene Wert als Referenz dient und Sie die .Syntax verwenden können, anstatt ->auf Mitgliedsvariablen zuzugreifen.

"Der Compiler erstellt automatisch Code, der das erste Mal durch die Deklaration und nicht danach erstellt, und löscht dann das statische Objekt beim Beenden des Programms."

Beachten Sie auch, dass Sie mit dem Meyers Singleton "in eine sehr schwierige Situation geraten können, wenn Objekte zum Zeitpunkt der Beendigung aufeinander angewiesen sind - wann verschwindet der Singleton relativ zu anderen Objekten? Bei einfachen Anwendungen funktioniert dies jedoch einwandfrei."


1

Zusätzlich zu der anderen Diskussion hier kann es erwähnenswert sein, dass Sie global sein können, ohne die Verwendung auf eine Instanz zu beschränken. Betrachten Sie zum Beispiel den Fall, dass eine Referenz etwas zählt ...

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 irgendwo in einer Funktion (wie z. B. 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 ihre jeweiligen speichern, Storeda diese Informationen zur Kompilierungszeit bereitgestellt werden. Sie müssen sich auch keine Sorgen um die StoreLebensdauer des Compilers machen , da der Compiler erfordert, dass er global ist. Wenn es tatsächlich nur eine Instanz von Storegibt, gibt es bei diesem Ansatz keinen Overhead. Bei mehr als einer Instanz ist es Sache des Compilers, bei der Codegenerierung klug zu sein. Bei Bedarf kann die ItemRefkann Klasse sogar eine gemacht werden friendvon Store(Sie Templat Freunde haben können!).

Wenn es sich Storeselbst um eine Vorlagenklasse handelt, werden die Dinge unübersichtlicher, aber es ist immer noch möglich, diese Methode zu verwenden, möglicherweise durch Implementieren einer Hilfsklasse mit der folgenden Signatur:

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 nun StoreWrapperfür jede globale StoreInstanz einen Typ (und eine globale Instanz) erstellen und immer über seine Wrapper-Instanz auf die Speicher zugreifen (wobei die wichtigen Details der für die Verwendung erforderlichen Vorlagenparameter vergessen werden Store).


0

Hier geht es um die Verwaltung der Objektlebensdauer. Angenommen, Ihre Software enthält mehr als 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 Logger zuletzt bereinigt wird. Lesen Sie daher auch dieses Dokument: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf


0

Meine Implementierung ähnelt der von Galik. Der Unterschied besteht darin, dass meine Implementierung es den gemeinsam genutzten Zeigern ermöglicht, den zugewiesenen Speicher zu bereinigen, anstatt den Speicher beizubehalten, bis die Anwendung beendet und die statischen Zeiger bereinigt werden.

#pragma once

#include <memory>

template<typename T>
class Singleton
{
private:
  static std::weak_ptr<T> _singleton;
public:
  static std::shared_ptr<T> singleton()
  {
    std::shared_ptr<T> singleton = _singleton.lock();
    if (!singleton) 
    {
      singleton.reset(new T());
      _singleton = singleton;
    }

    return singleton;
  }
};

template<typename T>
std::weak_ptr<T> Singleton<T>::_singleton;

0

Ihr Code ist korrekt, außer dass Sie den Instanzzeiger nicht außerhalb der Klasse deklariert haben . Die internen Klassendeklarationen statischer Variablen werden in C ++ nicht als Deklarationen betrachtet. Dies ist jedoch in anderen Sprachen wie C # oder Java usw. zulässig .

class Singleton
{
   public:
       static Singleton* getInstance( );
   private:
       Singleton( );
       static Singleton* instance;
};
Singleton* Singleton::instance; //we need to declare outside because static variables are global

Sie müssen wissen, dass die Singleton-Instanz von uns nicht manuell gelöscht werden muss . Wir benötigen ein einzelnes Objekt im gesamten Programm, sodass es am Ende der Programmausführung automatisch freigegeben wird.


-1

Das oben verlinkte Dokument beschreibt das Manko der doppelt überprüften Sperrung, dass der Compiler den Speicher für das Objekt zuweisen und einen Zeiger auf die Adresse des zugewiesenen Speichers setzen kann, bevor der Konstruktor des Objekts aufgerufen wurde. In C ++ ist es jedoch recht einfach, Allokatoren zu verwenden, um den Speicher manuell zuzuweisen, und dann einen Konstruktaufruf zu verwenden, um den Speicher zu initialisieren. Mit diesem Ansatz funktioniert die doppelt überprüfte Verriegelung einwandfrei.


2
Leider nicht. Dies wurde von einigen der besten C ++ - Entwickler ausführlich diskutiert. Die doppelt überprüfte Sperre ist in C ++ 03 fehlerhaft.
Martin York

-1
#define INS(c) private:void operator=(c const&){};public:static c& I(){static c _instance;return _instance;}

Beispiel:

   class CCtrl
    {
    private:
        CCtrl(void);
        virtual ~CCtrl(void);

    public:
        INS(CCtrl);

-1

Einfache Singleton-Klasse. Dies muss Ihre Header-Klassendatei sein

#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

Greifen Sie wie folgt auf Ihren Singleton zu:

sSingletonClass->Relocate(1, 2, 5);

-3

Ich denke, Sie sollten eine statische Funktion schreiben, bei der Ihr statisches Objekt gelöscht wird. Sie sollten diese Funktion aufrufen, wenn Sie Ihre Anwendung schließen möchten. Dadurch wird sichergestellt, dass kein Speicherverlust auftritt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.