Singleton Problem
-
volkard schrieb:
Morle schrieb:
hustbaer schrieb:
@dot
Der Anwendungsfall ist quasi immer "ich müsste ja sonst überall den Zeiger durchzeichen".Was übrigens bei modularer Entwicklung ein sehr legitimier Anwendungsfall ist.
Manchmal ein legitimer Fall für eine globale Variable. Die steckt dann aber in der cpp-Datei und gut ist's. Braucht keine Singleton-Tricks, sondern nur Zugriffsfunktionen.
Sry, dass ich jetzt etwas aushole: Natürlich *braucht* man keine Singletons.
Aber da steht z.B. das Durchschleppen von zig Pointern durch zig Klassen, nur weil man in einem unteren Layer Zugriff auf irgendwas
braucht gegen ein Singleton.
Oder auch die besagte globale Variable, die man vor jedem Zugriff manuell (oder die besagte Zugriffsfunktion) auf Gültigkeit testen muss.
In diesen Fällen bietet ein Singleton eine mehr oder weniger elegante Lösung für diese Probleme, die IMHO die anderen beiden Lösungen übertrifft.Das was mich hier leider oft stört ist der Eindruck, es gäbe immer nur *die eine einzig wahre Lösung*, was IMHO fernab von jeder Realität ist. Ich finde es schade für jeden Anfänger, der sich vielleicht ein Pattern wie Singleton nicht ansieht, weil er irgendwo gelesen hat, man soll lieber alle Pointer direkt durchschleppen.
z.B. eine DLL Instanz, bei der sich ein Singelton IMHO geradezu aufzwängt, weil es sie eben auch nur genau 1x im Speicher gibt
-
__thread schrieb:
volkard schrieb:
Morle schrieb:
hustbaer schrieb:
@dot
Der Anwendungsfall ist quasi immer "ich müsste ja sonst überall den Zeiger durchzeichen".Was übrigens bei modularer Entwicklung ein sehr legitimier Anwendungsfall ist.
Manchmal ein legitimer Fall für eine globale Variable. Die steckt dann aber in der cpp-Datei und gut ist's. Braucht keine Singleton-Tricks, sondern nur Zugriffsfunktionen.
Das ist nur ein legitimer Fall für eine globale
thread_local-Variable.Threadsicherheit erreicht man aber normalerweise nicht durch Singletons. Dafür gibt's Mutexe, Semaphoren, atomare Typen, ...
-
Morle schrieb:
hustbaer schrieb:
@dot
Der Anwendungsfall ist quasi immer "ich müsste ja sonst überall den Zeiger durchzeichen".Was übrigens bei modularer Entwicklung ein sehr legitimier Anwendungsfall ist.
Wobei Singletons ja gerade dem Prinzip der Modularität widersprechen, da man die Funktionalität nicht mehr lokal hält.
Zeiger immer durchreichen kann natürlich mühsam sein. Teilweise gibt es aber recht elegante Lösungen, wenn man die Zeiger verpackt oder Zwischen-APIs einbaut. Wenn man sich etwas achtet, kann man meiner Meinung nach die meisten Singletons vermeiden, ohne dass der Code besonders umständlich wird. Die Verlockung ist halt gross, den einfachen Weg zu wählen.
-
Nexus schrieb:
Zeiger immer durchreichen kann natürlich mühsam sein. Teilweise gibt es aber recht elegante Lösungen, wenn man die Zeiger verpackt oder Zwischen-APIs einbaut.
Ich wäre an der eleganten Lösung sehr interessiert. Ohne Durchreichen geht es IMHO nicht, denn wenn man mehrere Zeiger durchzureichen vermeiden will, reicht man wenigstens immer einen Zeiger auf irgendein "globales" Objekt durch, von dem man dann wieder die anderen Zeiger erhalten kann.
-
@Morle
Ja, nur dass das "globale" Objekt auf das man da einen Zeiger rumreicht eben nicht global ist. Genau das ist ja der Knackpunkt.----
Klar, Singletons sind nicht von Grund auf böse und zu nichts zu gebrauchen. Singletons sind aber fast immer eine Notlösung, ein unsauberer Hack der manchmal als Kompromiss Sinn macht. Singletons sind nichts was man sich angewöhnen sollte.
Die Fälle wo ich bisher gezwungen war ein Singleton zu verwenden hatten eigentlich immer als Ursache irgend eine Library die zu doof für irgendwas war.
Also z.B. Libraries dieInit()/Release()Calls haben die nicht mitzählen (=Release()gibt sofort frei, auch wenn man 2xInit()gemacht hat), Libraries die nicht threadsafe sind, Libraries die Callbackfunktionen ohnevoid* userdataParameter verwenden uswusf.Und in den (wenigen) Fällen wo ich bisher Singletons als sinnvolle Lösung angesehen (und dann auch verwendet) habe, hatten diese Singletons keinen mutable State. Beispielsweise eine Sammlung von Lookup-Tables die man per Code initialisieren möchte, aber nur wenn sie auch gebraucht werden.
(Wobei die Frage ist ob man das als Singleton implementieren muss, eincall_onceauf eine Initialisierungsfunktion hätte es wohl auch getan.)
-
Morle schrieb:
Ich wäre an der eleganten Lösung sehr interessiert.
Das ist natürlich von der Problemstellung abhängig.
Gib ein möglichst konkretes Beispiel, dann können wir gucken welche eleganten Möglichkeiten uns da einfallen.
-
hustbaer schrieb:
Morle schrieb:
Ich wäre an der eleganten Lösung sehr interessiert.
Das ist natürlich von der Problemstellung abhängig.
Gib ein möglichst konkretes Beispiel, dann können wir gucken welche eleganten Möglichkeiten uns da einfallen.z.B. das Beispiel, was ich vorher in diesem Thread gebracht habe:
Ein Hauptprogramm wird gegen 3 an sich unabhängige Module gelinkt, die jeweils als LIB vorliegen. Diese Module manipulieren alle auf irgendeine Weise Daten, die in Dateiform vorliegen. Dazu gibt eine (ältere, nicht threadsafe) DLL, die ein Datenmodell aus solchen Datendateien bauen kann, welches man dann wiederrum manipulieren und abspeichern kann.
Das Hauptprogramm sollte IMHO nichts davon wissen müssen, wie die einzelnen Module ihre Datenmanipulation vornehmen oder welche sonstigen Abhängigkeiten die Module intern haben. Weswegen die Singletonlösung im Moment verwendet wird.Dies ermöglichte unter anderem auch das stillschweigende Upgrade eines 4ten Moduls auf eine neuere DLL zur Datenmanipulation (die diese Probleme der älteren nicht hat), ohne das irgendwelche anderen Programmbestandteile geändert werden mussten.
-
Mit anderen Worten, statt Objekten werden DLLs verwendet. So eine Architektur habe ich zum Glück seit längerer Zeit nicht mehr sehen müssen.
Der vernünftige Weg, das umzusetzen, ist natürlich, eine gemeinsame Basisklasse zu definieren, die eine einheitliche Schnittstelle für was auch immer ein Modul halt können soll zur Verfügung stellt, und sich von der DLL per Factory-Funktion(en) ein Objekt/Objekte dieser Klasse geben zu lassen. Die DLL kennt intern eine Kindklasse dieser Basisklasse (die das Hauptprogramm nicht kennen muss) und erzeugt aus dieser ganz normal konkrete Objekte.
Die Funktionen, die deine DLLs derzeit exportieren, werden zu virtuellen Methoden der Basisklasse, und du kommst nicht in die Verlegenheit, in einer DLL veränderbaren, globalen Zustand halten zu müssen.
Im Übrigen ist, selbst wenn man eine derartige Architektur voraussetzt (und das sollte man wirklich nicht tun, wenn es sich irgendwie vermeiden lässt), nicht einzusehen, warum dein globales Objekt Singleton-Code drumherum braucht. Das Kind ist zwar mit "global" bereits in den Brunnen gefallen, also ist das nicht mehr das Hauptproblem, aber wenn du glaubst, es damit wieder rausfischen zu können, ist es nicht nur nass, sondern auch schief gewickelt.
-
seldon schrieb:
Der vernünftige Weg, das umzusetzen, ist natürlich, eine gemeinsame Basisklasse zu definieren, die eine einheitliche Schnittstelle für was auch immer ein Modul halt können soll zur Verfügung stellt, und sich von der DLL per Factory-Funktion(en) ein Objekt/Objekte dieser Klasse geben zu lassen. Die DLL kennt intern eine Kindklasse dieser Basisklasse (die das Hauptprogramm nicht kennen muss) und erzeugt aus dieser ganz normal konkrete Objekte.
So wird es in der neuen Variante gemacht. Ist an COM angelehnt.
Im Übrigen ist, selbst wenn man eine derartige Architektur voraussetzt (und das sollte man wirklich nicht tun, wenn es sich irgendwie vermeiden lässt), nicht einzusehen, warum dein globales Objekt Singleton-Code drumherum braucht.
Es "braucht" ihn nicht. Es ist aber für mich einfacher, denn ich kann im Clientcode schreiben:
MyDLL::GetInstance().Funktion(pDaten);Funktion sieht dann in etwa so aus:
void MyDLL::Funktion(void *p) { Lock lock(mLock); _dllFunktion(p); }Wie gesagt, es "geht" natürlich auch mit einem globalen Pointer, den ich dann erstmal auf Gültigkeit prüfen (was mir das Singleton abnimmt) und dann nochmal explizit das Lock anfordern muss (was ich auch mit freien Funktionen lösen könnte).
Die Singletonlösung ziehe ich dennoch vor, denn sie hält alles relevante einigermaßen elegant "zusammen". Aber vielleicht kommt ja hier noch ne andere Lösung
-
Ich sehe nicht, welchen Vorteil das gegenüber einem Namensraum hat.
// Header namespace MyDLL { void Funktion(void *p); } ... // Source namespace MyDLL { namespace { struct { Mutex mLock; // Mehr Kram, wenn notwendig. } global_data; } void Funktion(void *p) { Lock lock(global_data.mLock); _dllFunktion(p); } } ... // Anwendung MyDLL::Funktion(foo);Natürlich handelt es sich dabei im Grunde auch nur um Kosmetik. Es hindert ja nichts irgendwelchen Code daran, _dllFunktion (was übrigens ein reservierter Bezeichner ist, den du gar nicht verwenden darfst), direkt aufzurufen.
Das Grundproblem bleibt der globale Zustand, und dieses ganze Konstrukt ist ein ziemliches Kartenhaus. Gelöst werden müsste das in der DLL, um die es geht.
-
Ich hoffe wir reden jetzt nicht aneinander vorbei. Der Zugriffsfunktionen auf die DLL-Funktionen in die Singletonklasse zu packen lag lediglich nahe. Der eigentliche Zweck des Singletons ist aber sicherzustellen (nichts anderes macht MyDLL::GetInstance()), dass _dllFunktion ein gültiger Funktionspointer ist. Deine Lösung erfordert IMHO wenigstens noch ein if um die _dllFunktion herum. Wir bewegen uns hier aber wohl schon im Bereich des persönlichen Geschmacks.
-
Ich finde die Diskussion hier nicht zielführend. Warum? Weil hier überhaupt keine Basis dafür besteht, welche Nachteile Singletons haben ("Das weiß doch jeder" ist nicht adäquat, denn das stimmt 1. nicht und 2. braucht man dennoch eine gemeinsame Basis).
Ob Singleton in bestimmten Situationen dann ein Anti-Pattern ist oder nicht, spiegelt sich dann darin wieder, ob die Nachteile dort noch gelten. Das wäre Mal ein systematischer Ansatz.
Nehmen wir Mal an, wikipedia wäre eine gute Quelle und nehmen die Nachteile:
http://de.wikipedia.org/wiki/Singleton_(Entwurfsmuster)#Nachteile- Äquivalent zu globalen Variablen erstellen
=> globale Variablen sind der der Alternativvorschlag hier, fällt also weg- Verschleierte Abhängigkeit + Kopplung
=> Massive Argumente, trifft beides auch eingeschränkt auf globale Variablen zu, wobei die wenigstens im Kopf der Quellcodedateien auftauchen- Einzigartigkeit in MT-Umgebungen nicht gegeben (wurde ja oben bereits diskutiert)
- Das Testen eines Singleton kann kompliziert sein.
=> Das ist ein Nachteil- Achtung bei Initialisierungs-Reihenfolge
=> ebenso bei globalen Variablen- Eine Ressource-Deallokation von Ressourcen, die das Singleton verwendet, ist schwierig
=> Ist hier vermutlich egal, wäre bei einer globalen Variable aber wohl ähnlichOder kurz:
Was ist an einer globalen Variable so viel besser? Singletons als Anti-Patterns zu verneinen, aber globale Variablen einzuführen erfordert mMn eine gute Begründung.
-
@Morle: In dem Fall etwas wie (aufs Wesentliche zusammengestrichen)
// Header namespace MyDLL { void Funktion(void *p); } ... // Source namespace MyDLL { namespace { struct dirty_dll_handler { dirty_dll_handler(char const *dll_name, int flags) { dll_handle = dlopen(dll_name, flags); dllFunktion = static_cast<void(*)(void*)>(dlsym(dll_handle, "dllFunktion")); } Mutex mLock; void *dll_handle; void (*dllFunktion)(void*); }; dirty_dll_handler global_data("old_dirty.dll", RTLD_LAZY); } void Funktion(void *p) { Lock lock(global_data.mLock); global_data._dllFunktion(p); } }Ein Singleton ist da weder notwendig noch vorteilhaft. Die Klasse ist auch logisch kein Singleton, weil sie sich, wenn du mehrere solcher alten DLLs abhandeln musst, wiederverwenden ließe.
@Eisflamme: An einer globalen Variable ist nicht wirklich was besser als an einem Singleton. Es ist umgekehrt auch nicht wirklich was besser an einem Singleton als an einer globalen Variable. Wie gesagt, das Problem müsste eigentlich in der DLL gelöst werden, die selbst globale Variablen zu halten scheint.
-
Wie macht ihr Logging, wenn nicht als Singleton und es möglich sein soll, dass mehrere Thread ins gleiche Logfile schreiben, ohne das die Messages zerstückelt werden und das Logfile soll bis zum beenden immer geöffnet sein, damit keiner die Schreibrechte klaut? Eine Logging Klasse in jede andere Klasse durchzureichen ist doch total umständlich.
-
Man nehme ein globals std::ostream Objekt?
-
Nathan schrieb:
Man nehme ein globals std::ostream Objekt?
Also sowas wie std::cout!? Wieso sollte sowas ein Singleton sein?
-
Nathan schrieb:
Man nehme ein globals std::ostream Objekt?
Also wieder globale Variablen, statt Singletons. Weil Singletons jetzt böse sind und globale Variablen es früher waren, aber das schon alle vergessen haben.
-
Singleton und globale Variablen sind zwei völlig voneinander unabhängige Dinge...
-
dot schrieb:
Singleton und globale Variablen sind zwei völlig voneinander unabhängige Dinge...
Nein, in C++ zumindest kann man keine Singletons ohne globale/statische Variablen implementieren. Hatten wir übrigens schon

Oder anders ausgedrückt: Das Singleton-Pattern ist nicht die logische Konsequenz daraus, eine Klasse nur einmal instanziieren zu wollen. Zusätzlich muss auch das Bedürfnis nach einem gemeinsamen Zugriffspunkt bestehen.
-
dot schrieb:
Nathan schrieb:
Man nehme ein globals std::ostream Objekt?
Also sowas wie std::cout!? Wieso sollte sowas ein Singleton sein?
Ist eine globale Variable ein Singleton?
Bleiben wir beim Logger, std::cout, std::clog, std::cerr + eigener Logger sind alles Instanzen von std::ostream. Nach einer Instanz sieht das nicht gerade aus.looogggeer schrieb:
Nathan schrieb:
Man nehme ein globals std::ostream Objekt?
Also wieder globale Variablen, statt Singletons. Weil Singletons jetzt böse sind und globale Variablen es früher waren, aber das schon alle vergessen haben.
Wahl zwischen Pest und Cholera. :p
Nein, std::cout ist eine begründete globale Variable. Da gibt es auch keine Alternative, weil std::ostream kein Singleton ist.