Thread Safe Singleton - so richtig? noch tips?
-
Zu verbessern.
Nun ja, es gibt nicht immer einen Default-Constructor.Cyres schrieb:
Ist meine implementation eines Thread Safe Singleton so richtig? Was würdet ihr noch verbessern?
#pragma once #include <memory> #include <mutex> #include "Noncopyable.h" #include "Nonmoveable.h" template <typename T, typename InstantiationPolicy = LazyInstantiation> class Singleton : Noncopyable, Nonmoveable { static std::unique_ptr<T> instance_; static std::once_flag onceFlag; protected: Singleton() = default; public: static T& instance() { std::call_once(onceFlag,[]{InstantiationPolicy::create(instance_);}); return *instance_; } };
Warum liest eigentlich keiner der Singleton-Philosophen die Ausarbeitung von Alexandrescu zu dem Thema ?
-
Jeder Wald-Und-Wissen Progger müsste mittlerweile mitbekommen haben, das Singletons nicht benutzt werden sollten.
-
nurf schrieb:
sräsaif schrieb:
Wo ist der Unterschied zu
static S& s_instance() { static S instance; return instance; }
Da liegt der Fehler, die Instanz muss ein Pointer sein.
Siehe Alexandrescu für Details.Wieso muss da ein Pointer sein?
Mit nem c++11 konformen Kompilatör werden local statics thread-safe instanziert.Oder meinst du jetzt wegen der Sache mit dem evtl. nicht vorhandenen Default-Ctor?
-
Da wir hier von C++11 reden, ist die von sräsaif vorgeschlagene Variante threadsicher, und wenn man keine Instanziierungs-Policy braucht (die im ursprünglichen Post nicht verlangt war) kann man sich den ganzen Aufriss sparen. Dass die Instanz ein Zeiger sein müsse, ist nicht nachvollziehbar und streng genommen nicht möglich; man kann höchstens einen Zeiger auf die Instanz haben, was ohne Policy allerdings nicht wirklich nützlich ist.
Davon unabhängig aber:
1.) Singletons sind ein Anzeichen dafür, dass mit deinem Design was nicht stimmt,
2.) ist das kein Singleton, und
3.) sind auch davon abgeleitete Klassen keine Singletons, wenn sie das nicht selbst sicherstellen.Beispiel:
#include <boost/noncopyable.hpp> template <typename T> class Singleton : boost::noncopyable { protected: Singleton() {} public: static T& instance() { static T instance; return instance; } }; class Foo : public Singleton<Foo> { }; class Bar : public Singleton<Foo> { public: Singleton<Foo> &get_base_object() { return *this; } }; int main() { // Foo ist kein Singleton. Foo f, g, h; // Jedes Bar hat ein neues Singleton<Foo>, welches also auch kein Singleton ist. Bar a, b, c; Singleton<Foo> &r = a.get_base_object(); Singleton<Foo> &q = b.get_base_object(); Singleton<Foo> &s = c.get_base_object(); }
Die Singleton-Bedingung lässt sich nicht durch eine Basisklasse erzwingen. Was du hier "Singleton" nennst, ist eigentlich "KlasseMitGlobalerInstanz." Wobei die nur erstellt wird, wenn die instance()-Funktion mal aufgerufen wird, also ist auch der Name eigentlich nicht wirklich treffend.
-
hustbaer schrieb:
nurf schrieb:
sräsaif schrieb:
Wo ist der Unterschied zu
static S& s_instance() { static S instance; return instance; }
Da liegt der Fehler, die Instanz muss ein Pointer sein.
Siehe Alexandrescu für Details.Wieso muss da ein Pointer sein?
Mit nem c++11 konformen Kompilatör werden local statics thread-safe instanziert.Oder meinst du jetzt wegen der Sache mit dem evtl. nicht vorhandenen Default-Ctor?
Default Constructor ist eine Sache.
Aber es geht auch um das "static initialization order fiasco", wenn Singletons auf andere Singletons zugreifen, z.B. im Konstruktor.So kann man die Singleton auch in einer vorgesehenen Reihenfolge initializieren.
-
@nurf
Function-local statics werden beim 1. mal drüberlaufen initialisiert. Das static initialization order fiasco ist hier also kein Thema.
-
seldon schrieb:
Singletons sind ein Anzeichen dafür, dass mit deinem Design was nicht stimmt
Wie würdest du denn einen Logger erstellen, bzw. einen LoggerManager, bei dem sich verschiedene LoggerListener anmelden würden (bspw. ConsoleLogger)? Würde selbst das bei dir kein Singleton sein?
-
Gibt es denn irgendeinen Grund, wieso es bloß einen einzigen Logger geben kann? Oder ist es bloß, dass einer der Logger global sein soll und du mal gehört hast, dass globale Objekte schlecht sind, das dich zum Singleton geführt hat?
-
Wie nennt man eigentlich ein Singleton, das eigentlich gar kein Singleton ist? Also ein Objekt von einer Klasse, das man einfach nur in einen Singleton-Container gepropft hat, genau wie ein "echtes" Singleton, bloß dass auch andere Objekte von der Klasse verwendet werden "dürfen"?
Das ist doch sowieso eher der Ansatz den ich öfter sehe. Ich habe nur ganz selten echte Singleton-Klassen gesehen, mit privaten Konstruktoren usw.. (Wozu sollte man sich da auch einschränken?)
-
Es gibt nur einen einzigen LoggerManager, der verschiedene Logger registriert und sich merkt, welcher Logger wann was ausgibt (z.B. muss ein Logger für die Konsole ggf. andere Level beschreiben, als ein Filelogger oder ein Logger, der Fehlermeldung zurück an den Programmierer schreibt (bspw. Error oder Asserts). Anschließend wird über ein kleines Makro der LoggerManager benachrichtigt, dafür habe ich mir folgende Makros angelegt:
#define LOG_VERBOSE(msg) do { \ std::ostringstream os; os << (msg); \ LogManager::instance().logMessage(LogLevel::VERBOSE, __FILE__, __PRETTY_FUNCTION__, __LINE__, os.str());\ } while(0) #define LOG_DEBUG(msg) //... #define LOG_INFO(msg) //... #define LOG_WARNING(msg) //... #define LOG_ERROR(msg) //... #define LOG_ASSERT(msg) //...
Wenn es dort eine bessere Möglichkeit gibt, mit einem so einfachen Aufruf den Logger zu bedienen, ohne ein Singleton oder eine globale Variable zu verwenden, soll mir das recht sein
-
Schrecklich. Mach einfach eine ganz normale Klasse für den Log, und wenn du dann nen globalen Log haben willst, machste eben eine globale Instanz. Wo ist das Problem? Oo
-
Ja eben, es könnte doch auch unterschiedliche LoggerManager geben, beispielsweise für unterschiedliche Kontexte und so. Praktisch könnte ich mir ein Singleton vorstellen, wenn man in einem großen Projekt garantiert nur eine Instanz haben darf, welche eine Schnittstelle auf ein Gerät oder so darstellt, wobei das Gerät mit mehreren Verbindungen gleichzeitig nicht klarkommt, aber diese auch nicht abblockt.
Keine Ahnung, was das für ein Gerät sein soll. Ich glaube immer noch, Singleton wird einfach wegen Schreibarbeit-Sparung gerne genutzt.
-
Bedenkt, dass die meisten Leute, die (hier) von Singletons sprechen, eigentlich auch nur die etwas sicherere Variante von globalen Variablen meinen, und nicht wirklich den Anspruch auf die Sicherstellung von einer einzigen Klasseninstanz erheben. Zumindest sieht es meiner Erfahrung nach so aus, aber wer weiß...
-
Singletons werden für "ich will von überall bequem drauf zu greifen können" missbraucht
-
Der Wunsch ist doch legitim, und wenn man es dann auch so umsetzt, dann kann man auch schnell umbauen. Also ich find ja nichts verwerfliches dran... Aber man kann sich natürlich gut dran aufhängen...
-
Decimad schrieb:
Der Wunsch ist doch legitim (...)
Das Problem, in das du mit dieser Denkweise gerätst, ist, dass dein Code nur in ganz bestimmten Zusammenhängen verwendbar ist. Wenn du die Klasse, um die es geht, mal in einem anderen Zusammenhang verwenden willst, musst du sie umschreiben. Wenn mal doch ein Programmteil ein anderes Objekt als das bisher angelegte benutzen will (oder die Klasse in einer anderen Anwendung gebraucht werden könnte, in der das zentrale Logger-Objekt vielleicht nur etwas anders heißt), hat man auf die Weise eine echtes Problem.
Code Reusability = null. Der Ansatz war schon vor 30 Jahren nicht richtig sinnvoll, aber heute muss man es als Programmierer echt besser wissen.
-
Ich verstehe nicht, warum ich die Klasse selber umschreiben müsste? An der verändert sich doch überhaupt nichts. Es geht doch darum, dass die "Verwender der Klasse" dann eben umgeschrieben werden müssen, weil sie eben nicht über ein Singleton an das eine Objekt kommen, sondern von irgendwo gesagt bekommen, welches Objekt sie verwenden sollen. Ist doch das gleiche als ob ich jeder Funktion nen ostream mitreiche oder in den Funktionen eben cout verwende. Ist ostream schlecht, weil ich irgendwo cout direkt verwende (und nicht einfach nur so, hehe)?
Um es mal in die Perspektive zu rücken: Ich habe in wachsenden Designs einige Refaktorisierungen hinter mir, die durchaus mal mehrere Tage in Anspruch genommen haben. Diese Singleton-Sachen, die ich abgebaut habe, haben mich nirgends mehr als 20 Minuten gekostet. Aber die Diskussionen über Singletons im Internet erwecken den Eindruck als wären Singletons der heilige Gral des schlechten Designs. Schlechtes Design definiert sich bei mir über die Kosten, es zu bereinigen und da ist das Brimborium über Singletons einfach nicht verhältnismäßig.
-
Decimad schrieb:
Wie nennt man eigentlich ein Singleton, das eigentlich gar kein Singleton ist?
Du meinst eine globale Variable!?
Decimad schrieb:
Ich verstehe nicht, warum ich die Klasse selber umschreiben müsste?
So ein Singleton infiziert langsam aber stetig sämtlichen Code, der es benutzt. Das Singleton selbst ist vielleicht wiederverwendbar, aber der Code, der das Singleton benutzt nicht...
-
Decimad schrieb:
Ich habe in wachsenden Designs einige Refaktorisierungen hinter mir, die durchaus mal mehrere Tage in Anspruch genommen haben.
Das ist gar nix. Mein Refactoring was gerade für das komplette Design meines 3D4Wins-Spaßprojekts läuft, wird mehrere Wochen - wenn nicht länger - dauern.
Singletons werden tatsächlich verpönt, aber das hat einen Grund: Man braucht sie nicht.
Mach einfach eine ganz normale Klasse für den Log, und wenn du dann nen globalen Log haben willst, machste eben eine globale Instanz. Wo ist das Problem? Oo
Ganz einfach.
Aber wenn man wirklich nur eine Instanz haben will, hätte ich da noch eine Idee.
Ich muss, um ein Objekt vom Typ ... zu erzeugen, eine Ressource bereitstellen mit der ... arbeiten kann. Wie
std::cout
einen speziellen streambuf hat, der das Schreiben auf die Konsole kapselt. So muss auch der Logger eine Art Schnittstelle haben, mit der es auf den Bildschirm schreibt, oder in die Datei, oder sonst wohin. Es ist dann nicht direkt möglich, eine zweite Instanz zu erstellen, weil man dafür einen Zeiger oder eine Referenz auf die Ressource braucht.Der Trick ist, dass die Ressource aber über einen opaken Zeiger gehandelt wird, und der User davon keine Ahnung hat.
Sprich, er kann zwar das globale Objekt benutzen, aber er kann keine ...-Instanz erzeugen weil die Ressource gar nicht definiert ist.Die globale Variable und die Memberfunktionen von ... wird irgendwo definiert, wo die Schnittstelle auch definiert ist. Aber das sieht der User nicht.
Später, wenn man es braucht, dann kann man beliebig viele globale Variablen erzeugen.
P.S.: Ich hab ein Wortspiel: Hoffentlich ist Decimad nicht mehr Decimad auf mich.
-
Wir sollten zu einer weltweiten Bücherverbrennung des Buchs "Design Patterns. Elements of Reusable Object-Oriented Software" aufrufen, damit Singletons langsam aber sich aussterben.