Singleton Problem
-
Moin,
mich bringt gerade folgendes Problem zum Verzweifeln:
Ich habe eine Singleton Klasse implementiert
und möchte nun auf eine Instanz dieser an verschiedenen Orten zugreifen
(verschiedene Klassen in verschiedenen Projekten).Leider gibt es ein Projekt (GUI), wo der getInstance() Zugriff eine neue Singleton Instanze erstellt, obwohl bereits eine vorhanden ist.
Folgender Ablauf:
(project vs) main.cpp -> ruft das erste mal getInstance() auf -> Das Teil wir erzeugt
(project vs) main.cpp -> zum Testen noch mal zweimal getInstance() aufrufen -> nichts neues erzeugt, gleich Speicheradressen zurückgegeben
[in der main.cpp wird eine Instanz einer gui Klasse erstellt]
(project gui) -> getInstance() sagt angeblich noch keins vorhanden und legt eine neue Instanz an
(Project gui) -> nochmal getInstance() zum Testen aufrufen -> jetzt ist eine Instanz vorhanden, diese zeigt natürlich auf die Instanz von der GUI und nicht auf die von der Main
[in der main.cpp wird eine Instanz einer algo Klasse erstellt]
(Project algo) -> getInstance() aufrufen und man bekommt die Instanz von der main.cpp zurückAlso die Singleton Klasse scheint an sich zu funktionieren (hab den Code mehrmals mit diversen Beispielen aus dem Internet verglichen), allerdings schafft es ein Projekt dummes Zeug anzustellen.
Aber wie ist sowas möglich?
VG Sven
-
Ach Mist..
kann es sein dass da der Hase begaben liegt?
"Werden dynamische Bibliotheken wie DLLs verwendet, muss u. U. sichergestellt werden, dass auch hier nur eine Instanz existieren kann."Wenn ja, wie kann ich sowas sicher stellen?
-
icewater schrieb:
Ich habe eine Singleton Klasse implementiert
Well, there's your problem...
icewater schrieb:
kann es sein dass da der Hase begaben liegt?
"Werden dynamische Bibliotheken wie DLLs verwendet, muss u. U. sichergestellt werden, dass auch hier nur eine Instanz existieren kann."dlls sind separate Module und haben daher ihre eigenen Instanzen statischer Variablen.
icewater schrieb:
Wenn ja, wie kann ich sowas sicher stellen?
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. Wenn du keinen Grund dafür finden kannst, dann ist Singleton nicht was du willst, denn der einzige Sinn von Singleton ist, genau das und nur das sicherzustellen (hence the name)...
"Ich brauch grad nur eins davon" und "es darf rein aus Prinzip niemals mehr als ganz genau eins davon geben" sind zwei völlig verschiedene Dinge; Singleton ist für letzteres gedacht. Wenn du dir jetzt denkst, dass das dann ja wohl bedeutet, dass es praktisch keinen Anwendungsfall für Singleton gibt, dann hast du absolut recht. Ich zumindest warte nun seit über 10 Jahren drauf, mal einen zu finden...
-
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.
-
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!?
-
dot schrieb:
Ich bin ganz Ohr, vielleicht kannst du mir und den anderen Unwissenden hier ja die Augen öffnen!?
Daran wäre ich auch interessiert. Vor allem wie man von der Bedingung, dass nur ein Objekt existieren darf, auf die Idee kommt, dieses global zu machen und sämtliche damit verbundenen Nachteile in Kauf zu nehmen.
-
boost::pool_allocator nutzt intern ein Singleton
-
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!?
Anwendungsfall "Interface für Single-Threaded-DLL in einer Multithreaded Umgebung": Es gibt eine C++ Klasse, die den Zugriff auf alle DLL Funktionen kapselt und gleichzeitig durch Locking sicherstellt, dass nicht 2 Threads gleichzeitig irgendwelche DLL Funktionen aufrufen. Diese Klasse ist als Singelton implementiert.
-
@dot
Der Anwendungsfall ist quasi immer "ich müsste ja sonst überall den Zeiger durchreichen".
-
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.
-
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.
-
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.
-
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