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.
c++
design-patterns
singleton
Artem Barger
quelle
quelle
Antworten:
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 .
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.
quelle
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 alsSchwarz 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). Jaget_instance()
ist viel hässlicher.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.
quelle
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):
Es gibt keine dynamische Speicherzuordnung.
quelle
@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_ptr
kann der Singleton für alle Benutzer am Leben erhalten werden, auch wenn die statischen Destruktoren am Ende des Programms aufgerufen werden:quelle
Eine weitere nicht zuweisende Alternative: Erstellen Sie einen Singleton, z. B. eine Klasse
C
, nach Bedarf:mit
Weder diese noch die Antwort von Cătălin sind in aktuellem C ++ automatisch threadsicher, sondern in C ++ 0x.
quelle
Ich habe unter den Antworten keine CRTP-Implementierung gefunden, daher hier:
Um zu verwenden, erben Sie einfach Ihre Klasse davon, wie:
class Test : public Singleton<Test>
quelle
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 werdenmain
.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
QApplication
in dermain
Funktion 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()
undterm()
Methoden für alle Singletons und rufe sie inmain
. 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 hatgetInstance()
oder nicht.quelle
Hier ist eine einfache Implementierung.
Es wird nur ein Objekt erstellt und diese Objektreferenz wird jedes Mal nach den Nachwörtern zurückgegeben.
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.
quelle
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.
quelle
m_s
ein Lokalstatic
zugetInstance()
erstellen und es sofort ohne Test zu initialisieren.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:
... 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.
quelle
Hat jemand erwähnt
std::call_once
undstd::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_flag
ist der Mechanismus, um eine sichere Initialisierung zu gewährleisten.quelle
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.
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.
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."
quelle
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 ...
Jetzt können Sie irgendwo in einer Funktion (wie z. B.
main
) Folgendes tun:Die Refs müssen keinen Zeiger zurück auf ihre jeweiligen speichern,
Store
da diese Informationen zur Kompilierungszeit bereitgestellt werden. Sie müssen sich auch keine Sorgen um dieStore
Lebensdauer des Compilers machen , da der Compiler erfordert, dass er global ist. Wenn es tatsächlich nur eine Instanz vonStore
gibt, 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 dieItemRef
kann Klasse sogar eine gemacht werdenfriend
vonStore
(Sie Templat Freunde haben können!).Wenn es sich
Store
selbst 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:Der Benutzer kann nun
StoreWrapper
für jede globaleStore
Instanz 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 werdenStore
).quelle
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
quelle
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.
quelle
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 .
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.
quelle
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.
quelle
Beispiel:
quelle
Einfache Singleton-Klasse. Dies muss Ihre Header-Klassendatei sein
Greifen Sie wie folgt auf Ihren Singleton zu:
quelle
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.
quelle