Singleton Problem
-
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.
-
Ach, hier stand Käse.
-
@Morle
Jo.
Dein Beispiel fällt für mich in die selbe Kategorie wie auch non-nesting Init/Release Funktionen.Bei sowas finde ich nen "Singleton" als Workaround OK. Wobei ich hier auch normale freie Funktionen als Wrapper vorziehen würde.
Irgendwo muss es aber irgend ein "Singleton" geben, weil es darf ja nur eine Mutex geben. (Wobei dieses "Singleton" nicht unbedingt das "Singleton-Pattern" verwenden muss, es tut's auch eine globale Variable +call_onceo.ä.)Welche Dinge als Workaround OK sind oder nicht finde ich aber nicht wirklich ein sehr spannendes Thema. Was sein muss muss eben sein.
Hast du auch ein Beispiel wo ein Singleton vorkommt das nicht Teil eines Workarounds ist?
pS: Bei deinem Beispiel einen Zeiger auf irgend eine Wrapper-Klasse rumzureichen wäre mMn. komplett verkehrt. Würde nur einen weiteren unnötigen Bruchpunkt schaffen.
-
Nexus schrieb:
Oder anders ausgedrückt: Das Singleton-Pattern ist nicht die logische Konsequenz daraus, eine Klasse nur einmal instanziieren zu wollen.
Natürlich nicht, das hab ich auch nie behauptet, ganz im Gegenteil. Zweck des Singleton Pattern ist es, auszudrücken, dass es sich um eine prinzipielle Eigenschaft eines Typs handelt, dass es immer nur eine Instanz dieses Typs geben kann (vgl. mit dem gleichnamigen Konzept aus der Mathematik). Der globale Zugriffspunkt (der streng genommen übrigens eigentlich nicht global ist, da er nicht globalen Scope hat; aber ich denk, wir wissen beide, was damit gemeint ist) ist imo lediglich als Konsequenz der üblichen Implementierung eines Singleton zu sehen und auf keinen Fall ist das Bedürfnis nach einem globalen Zugriffspunkt hinreichende Bedingung für die Anwendung des Singleton Pattern. Das Wesen des Singleton steckt in der Konstruktionsbeschränkung; die ist, was ein Singleton ausmacht, ohne die gibt's per Definition kein Singleton. Und Konstruktionsbeschränkung und globaler Zugriffspunkt sind zwei völlig orthogonale Konzepte...
Nathan schrieb:
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?
Wie oben geschrieben sind "globale Variable" und "Singleton" unabhängige Konzepte. Eine globale Variable ist ein Objekt, als Singleton bezeichnet man dagegen einen Typ mit bestimmten Eigenschaften. Natürlich könnte der Typ einer globalen Variable rein prinzipiell ein Singleton sein, auch wenn es in C++ vermtulich keinen Weg gibt, sowas direkt zu bauen. Aber weder "ist" eine globale Variable notwendigerweise ein Singleton, noch ist die eine Instanz eines Singleton notwendigerweise eine globale Variable...
-
dot schrieb:
xdrectfvzgbh schrieb:
dot schrieb:
Die richtige Frage ist eher, was es genau für einen Grund gibt, aus dem sichergestellt werden muss, dass die fragliche Klasse um jeden Preis nur einmal instanziert werden kann. Oder anders ausgedrückt: Sag mir, wieso es in der Geschichte der Menschheit nie jemals ein zweites Objekt dieser Klasse wird geben dürfen.
Du hast Singleton nicht verstanden. Das hat nichts mit der Geschichte der Menschheit zu tun, sondern nur mit der Programminstanz. Darum findest du auch nie einen Anwendungsfall.
Ich bin ganz Ohr, vielleicht kannst du mir und den anderen Unwissenden hier ja die Augen öffnen!?
Jedes Ding, dass es für eine spezielle Anwendung nur einmal geben soll und von dem jeder Entwickler wissen soll, dass er hier genau dieses verwenden soll ist ein Singleton. Wenn bestimmte Resourcen nur einmal geladen werden sollen, mache ich ein Singleton, dass diese cached (das bedeutet nicht, jeder Cahce ist ein Singleton (ja ich weiß, du willst alles falsch verstehen)). Du wirts diesen Cache zwar viel lieber an 10000 Klassen weiter geben und dort als Member halten, aber das machen dann sowieos wieder andere Entwickler falsch und erstellen sich ihren eigenen Cache, was dem Anwendungskonzept widerspricht. Aber du entwickelst sowieso nur allein, stimmst?
-
Wie machst du das dann, wenn du Unit-Tests schreibst oder den selben Code mal in mehreren Anwendungen verwenden willst?
Und wenn du mit Entwicklern arbeitest, die es nicht hinkriegen, ein übergebenes Objekt zu verwenden, kannst du dich dann wirklich darauf verlassen, dass die mit deinem Wald von Singletons zurechtkommen? Mit Verlaub, dieses Konzept klingt wie die Rückkehr zur schlechten alten Zeit vor der Erfindung der Objektorientierung.