900 Stimmen

C++ Singleton-Entwurfsmuster

Kürzlich bin ich auf eine Realisierung/Implementierung des Singleton-Designmusters für C++ gestoßen. Sie sah wie folgt aus (ich habe sie 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 Erklärung kann ich ableiten, dass das Instanzfeld auf dem Heap initiiert wird. Das heißt, es findet eine Speicherzuweisung statt. Was für mich völlig unklar ist, ist, wann genau der Speicher wieder freigegeben werden soll? Oder gibt es einen Fehler und ein Speicherleck? Es scheint, als gäbe es ein Problem mit der Implementierung.

Meine Hauptfrage ist: Wie setze ich es richtig um?

14voto

Yuriy Punkte 601

Ich habe unter den Antworten keine CRTP-Implementierung gefunden, also hier ist sie:

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

Zur Verwendung erben Sie einfach Ihre Klasse von dieser, wie: class Test : public Singleton<Test>

10voto

Tony Bai Punkte 186

Wir haben dieses Thema kürzlich in meinem EECS-Kurs besprochen. Wenn Sie sich die Vorlesungsunterlagen im Detail ansehen möchten, besuchen Sie http://umich.edu/~eecs381/vorlesung/IdiomsDesPattsCreational.pdf . Diese Notizen (und Zitate, die ich in dieser Antwort wiedergebe) wurden von meinem Professor, David Kieras, erstellt.

Meines Wissens gibt es zwei Möglichkeiten, eine Singleton-Klasse korrekt zu erstellen.

Erster Weg:

Implementieren Sie es so, wie Sie es in Ihrem Beispiel haben. Was die Zerstörung angeht: "Singletons bestehen in der Regel für die Dauer des Programmlaufs; die meisten Betriebssysteme stellen den Speicher und die meisten anderen Ressourcen wieder her, wenn ein Programm beendet wird, also gibt es ein Argument, sich darüber keine Gedanken zu machen."

Es ist jedoch gute Praxis, bei Programmende aufzuräumen. Daher können Sie dies mit einer statischen Hilfsklasse SingletonDestructor tun und diese 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; }
};

// somewhere in code (Singleton.cpp is probably the best place) 
// create a global static Singleton_destroyer object
Singleton_destoyer the_destroyer;

Der Singleton_destroyer wird beim Programmstart erstellt, und "wenn das Programm beendet wird, werden alle globalen/statischen Objekte durch den Shutdown-Code der Laufzeitbibliothek (eingefügt durch den Linker) zerstört, so dass der_destroyer zerstört wird; sein Destruktor löscht das Singleton, indem er seinen Destruktor ausführt."

Zweiter Weg

Dies wird Meyers Singleton genannt und wurde vom C++-Zauberer Scott Meyers entwickelt. Definieren Sie get_instance() einfach anders. Jetzt können Sie auch die Zeigervariable loswerden.

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

Das ist praktisch, weil der zurückgegebene Wert ein Verweis ist und Sie mit . Syntax anstelle von -> um auf Mitgliedsvariablen zuzugreifen.

"Der Compiler erstellt automatisch Code, der 's' beim ersten Mal durch die Deklaration erzeugt, danach nicht mehr, und löscht dann das statische Objekt bei Programmende."

Beachten Sie auch, dass Sie mit dem Meyers-Singleton "in eine sehr schwierige Situation geraten können, wenn Objekte zum Zeitpunkt der Beendigung voneinander abhängen Beendigung aufeinander angewiesen sind - wann verschwindet das Singleton im Verhältnis zu anderen Objekten? Aber für einfache Anwendungen funktioniert das gut."

8voto

Hier ist eine einfache Umsetzung.

#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 dieser Objektverweis wird danach jedes Mal zurückgegeben.

SingletonClass instance created!
00915CB8
00915CB8

Hier ist 00915CB8 die Speicherposition des Singleton-Objekts, die für die Dauer des Programms gleich bleibt, aber (normalerweise!) bei jedem Programmdurchlauf anders ist.

N.B. Es handelt sich hierbei nicht um eine fadensichere Version, Sie müssen die Fadensicherheit gewährleisten.

8voto

Red.Wave Punkte 1774

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

Ein großes Problem bei der Implementierung des Singleton-Musters ist die sichere Initialisierung. Der einzige sichere Weg ist, die Initialisierungssequenz mit synchronisierenden Barrieren zu schützen. Aber diese Barrieren selbst müssen sicher initiiert werden. std::once_flag ist der Mechanismus für eine garantiert sichere Initialisierung.

7voto

SadSido Punkte 2451

Die Lösung in der akzeptierten Antwort hat einen erheblichen Nachteil - der Destruktor für das Singleton wird aufgerufen, nachdem die Kontrolle die main() Funktion. Es kann tatsächlich Probleme geben, wenn einige abhängige Objekte innerhalb von main .

Ich bin auf dieses Problem gestoßen, als ich versuchte, ein Singleton in die Qt-Anwendung einzuführen. Ich beschloss, dass alle meine Setup-Dialoge Singletons sein müssen, und übernahm das obige Muster. Leider ist die Hauptklasse von Qt QApplication wurde auf dem Stapel in der main Funktion, und Qt verbietet es, Dialoge zu erstellen/zu zerstören, wenn kein Anwendungsobjekt vorhanden ist.

Deshalb bevorzuge ich Heap-allocated Singletons. Ich biete eine explizite init() y term() Methoden für alle Singletons und rufen sie innerhalb von main . So habe ich die volle Kontrolle über die Reihenfolge der Erstellung/Zerstörung von Singletons, und ich garantiere auch, dass Singletons erstellt werden, egal ob jemand getInstance() oder nicht.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X