Singleton verwenden oder nicht?



  • Hallo Leute,

    ich möchte gerne ein Objekt für Diagnoseaufgaben global verfügbar machen. Dafür würde sich scheinbar ein Singleton eignen. Allerdings lese ich immer wieder in diversen Foren oder Blogs, dass dis nicht so toll sei. Es mach den Code wohl schwer testbar, schafft Abhängigkeiten und verführt zu prozedualen Ansätzen.....

    Ich habe noch nie mit Singleton gearbeitet und wollte mal eure Meinung hören. Sollte ich etwas anderes verwenden? ...oder gibt es in C++ 11 irgendwelche Möglichkeiten die besser sind?

    viele Grüße,
    SBond



  • Du wirst auch in diesem Forum nichts anderes hören.
    Aber du scheinst dich ja damit beschäftigt zu haben und kannst selber abwägen.

    Ich kann dir nur raten, dir nicht selber Denkverbote zu geben.
    Ich hab das erst kürzlich noch gehabt: Singeleton sind böse, daher statt dessen dicken Kommentar "Bitte bitte bitte keine 2 Instanzen erstellen! Das gibt ne Katastrophe!".
    Kannst dir wohl denken, was passiert ist...
    (jetzt wird kommen, dass ich die Klasse hätte anders designen müssen.
    Möglich, wüsste aber nicht wie und das Singleton macht genau das, was es soll).



  • Also ich weiß nicht was an einem Singlton böse sein soll, wenn ich das Singlton nunmal brauche. 🙄 Wenn ich es nicht brauche, mache ich keines.

    Aus langjähringen Projekten, weiß ich, das es Probleme geben kann. Aber nicht, weil das Sington böse ist, sondern weil man es eingesetzt hatte, obwohl man später doch mehrere Objekte benötigte. Aber ist das die Schuld vom Singlton Pattern? 😕

    Wo steht denn, das es nicht nur böse ist, sondern es auch ein konkretes Beispiel gibt, was böses passieren kann? Und vorallem: was ist die Alternative zum Singlton?



  • Meine unbedeutende Meinung:

    Die Aufgabe des Singletons ist es, dafür zu sorgen, dass es nur eine Instanz einer Klasse gibt. Daran finde ich noch nichts böses.

    Die statische Methode, die diese Instanz liefert verführt allerdings dazu das Singleton fälschlich als "globale Variable" zu verwenden, eben weil es einfach ist.

    Es geht also eher darum, wie das Singleton verwendet wird.

    Was spricht dagegen, die Singleton-Instanz an die verwendende Klasse per DI als ein Interface zu übergeben?



  • Ich benutze Singletons auch und kann die Argumente gegen Singletons teilweise nicht nachvollziehen. Bisher gab´s aber auch keine konkreten Vorschläge, wie man ein Singleton ersetzt. Globale Variable? Nein! Objekt als Funktionsparameter quer durch die Anwendung reichen? Nein!
    Ich hatte erst ein Mal ein Problem mit einem Singleton, weil der Compiler den ersten Instanziierungsaufruf wegoptimiert hat und ein nebenläufiger Thread den ersten Zugriff gemacht hat. War, zugegebenermaßen, fies zu finden, weil das Problem erst im Release Build auftrat.



  • Ein Argument gegen Singleton(s) sind die versteckten Abhängigkeiten.

    Nehmen wir an wir haben diese Klasse, am Konstruktor sieht man nun, dass diese Klasse Abhängigkeiten zu den beiden Interfaces hat (egal ob das nun Interfaces, abstrakte Klassen oder Konkrete Klassen sind).

    class Foo
    {
    public:
    	Foo(Interface1 const& i1, Interface1 const& i2);
    
    	void bar();
    };
    

    Wenn nun in der Implementierung sowas steht:

    void Foo::bar()
    {
    	i1.someFoo();
    	i2.someMethod();
    
    	Interface3::getInstance().someStuff();
    }
    

    Dann ist diese Abhängigkeit von aussen durch das Interface der Foo Klasse nicht sichtbar, sondern nur wenn man in die Implementierung schaut. Und es reicht ja, wenn diese Singleton Methode nichts an ihrem internen State ändern, sondern nur was zurückgibt z.B., trotzdem ist diese Abhängigkeit da aber versteckt.

    Jetzt kann man diskutieren, ob das das Verhalten oder die Nutzung einer globalen Variable ist oder nicht. Wenn nicht, stellt sich mir aber die Frage, was denn dann eine globale Variable ausmacht.

    Ein guter Artikel dazu, wie ich finde, ist von Misko Hevery.



  • Sehe ich genauso. 👍

    Es darf ja ruhig Singletons geben, aber der Zugriff darauf sollte immer über Schnittstellen erfolgen, alleine schon, um vernünftige Unit-Tests schreiben zu können (meistens ist es ja so, daß Singletons benutzt werden, um externe Resourcen, z.B. Filesystem, anzusprechen).



  • Skym0sh0 schrieb:

    Ein Argument gegen Singleton(s) sind die versteckten Abhängigkeiten.

    Nehmen wir an wir haben diese Klasse, am Konstruktor sieht man nun, dass diese Klasse Abhängigkeiten zu den beiden Interfaces hat (egal ob das nun Interfaces, abstrakte Klassen oder Konkrete Klassen sind).

    class Foo
    {
    public:
    	Foo(Interface1 const& i1, Interface1 const& i2);
    
    	void bar();
    };
    

    Wenn nun in der Implementierung sowas steht:

    void Foo::bar()
    {
    	i1.someFoo();
    	i2.someMethod();
    
    	Interface3::getInstance().someStuff();
    }
    

    Dann ist diese Abhängigkeit von aussen durch das Interface der Foo Klasse nicht sichtbar, sondern nur wenn man in die Implementierung schaut. Und es reicht ja, wenn diese Singleton Methode nichts an ihrem internen State ändern, sondern nur was zurückgibt z.B., trotzdem ist diese Abhängigkeit da aber versteckt.

    Was hat das jetzt speziell mit Singleton zu tun? Interface3 könnte eine x-beliebige Klasse mit statischen Methoden sein. Es könnte sogar eine beliebige Klasse mit nicht-statischen Methoden sein, um eine versteckte Abhängigkeit zu bewirken.



  • Skym0sh0 schrieb:

    Dann ist diese Abhängigkeit von aussen durch das Interface der Foo Klasse nicht sichtbar, sondern nur wenn man in die Implementierung schaut. Und es reicht ja, wenn diese Singleton Methode nichts an ihrem internen State ändern, sondern nur was zurückgibt z.B., trotzdem ist diese Abhängigkeit da aber versteckt.

    Ich stehe ehrlich gesagt auf dem Schlauch. Was ist die schlimme Folge, von dem was du im Code gezeigt hast? 😕

    Skym0sh0 schrieb:

    Jetzt kann man diskutieren, ob das das Verhalten oder die Nutzung einer globalen Variable ist oder nicht. Wenn nicht, stellt sich mir aber die Frage, was denn dann eine globale Variable ausmacht.

    Das Singleton Pattern hat nichts mit Globale Variable zu tun. Es ist keine Alternative und auch keine bessere globale Variable. Das Singleton Pattern soll von einer Klasse erlauben nur EIN Objekt instanzieren zu können. Es geht nicht darum sich zu ersparen, Objekte nicht durchreichen zu müssen. Wer das in einem Singelton Pattern sieht, brockt sich damit natürlich unweigerlich Probleme ein.

    Globale Variablen/Objekte können dagegen von einer Klasse öffters vorkommen!

    Wahrscheinlich ist genau das Problem am Singlton Pattern: es wird oft falsch verstanden und somit falsch eingesetzt. Ist aber nicht die Schuld vom Pattern. :p 🙂



  • DocShoe schrieb:

    Ich hatte erst ein Mal ein Problem mit einem Singleton, weil der Compiler den ersten Instanziierungsaufruf wegoptimiert hat und ein nebenläufiger Thread den ersten Zugriff gemacht hat. War, zugegebenermaßen, fies zu finden, weil das Problem erst im Release Build auftrat.

    Das ist mal ein sachlicher Hinweis. Sollte man im Hinterkopf behalten.
    Wie hast du das gelöst?



  • bei mir kam die Überlegung für ein Singleton zur Realisierung bestimmter Anwendungsgebiete. Beispielsweise detaillierte Konsolenausgaben, Log-Funktionalität oder Diagnosefunktionen.

    Nehmen wir mal beispielsweise eine Log-Funktion. Viele Methoden unterschiedlicher Objekte sollten in der Lage sein, einfache Texteinträge in eine Logdatei zu schreiben. Wie macht man das am besten? Ich habe schon viele Varianten gesehen:

    1. Variante: Verwendung von #ifdef/#endif

    // ...irgendwelcher code
    
    #ifdef DEBUG
      // irgenwelche Routinen für Konsolenausgaben oder zum Loggen
    #endif
    
    // ...irgendwelcher code
    
    #ifdef DEBUG
      // irgenwelche Routinen für Konsolenausgaben oder zum Loggen
    #endif
    
    // ...irgendwelcher code
    

    Solche Versionen mögen eventell den Code im Release-Build verringern, aber ich finde sowas unleserlich (insbesondere bei exzessiven Gebrauch)

    2. Variante: Verwendung von statischen Klassen. Ähnelt gewisserweise dem Singleton (subjektiv empfunden; mir fehlt da noch etwas Erfahrung)

    Logger::error("ein Fehler ist aufgetreten");
    

    3. Variante: Verwendung von Makros. Hatte damit schon einige ungünstige Erfahrungen gesammelt.

    LOG_ERR("ein Fehler ist aufgetreten");
    

    4. Variante: Verwendung von Singleton.

    Logger::getInstance()->error("ein Fehler ist aufgetreten");
    

    5. Variante: eiskalt globale Variablen verwenden. Naja muss ja nicht sein.

    =========================================

    tja. Nun würde ich mich an einem Singleton versuchen. 😕


  • Mod

    Du hast genau den Plan, weswegen Singletons so einen schlechten Ruf haben: Missbrauch als globale Variable. Du willst das Singleton nicht haben, weil dein Logger wirklich einzigartig sein müsste (Wieso sollte er das sein?), sondern weil du über die globale Funktion bequem an eine Loggerinstanz kommen möchtest.



  • SBond schrieb:

    Wie macht man das am besten? Ich habe schon viele Varianten gesehen

    Ich würde vom Prinzip so etwas in der Art machen...

    class ILogger
    {
        public:
            virtual ~ILogger() = default;
            virtual void log(const string& text) {};
    };
    
    class Base
    {
            ILogger logger;
        public:
            void setLogger(ILogger logger);
    };
    
    class Foo : public Base
    {
        public:
            void doSomething()
            {
                // zeug
    
                logger.log(text);
    
                // mehr zeug
                }
            }
    };
    
    int main(int argc, char *argv[])
    {
        ConsoleLogger logger;
    
        // oder falls der Logger ein Singleton sein soll/muss
    
        auto logger = SingletonLogger::getInstance();
    
        Foo foo;
        foo.setLogger(logger); // wird kein Logger zugewiesen, dann wird die leere Implementierung von ILogger verwendet = kein Log
    
        foo.doSomething();
    }
    


  • DocShoe schrieb:

    Ich benutze Singletons auch und kann die Argumente gegen Singletons teilweise nicht nachvollziehen.

    Dann verwendest du sie nicht lange genug.

    DocShoe schrieb:

    Bisher gab´s aber auch keine konkreten Vorschläge, wie man ein Singleton ersetzt. Globale Variable? Nein!

    Natürlich nicht, das wäre ja die Pest mit Cholera zu bekämpfen.

    DocShoe schrieb:

    Objekt als Funktionsparameter quer durch die Anwendung reichen? Nein!

    Doch, genau das! 🙄

    DocShoe schrieb:

    Ich hatte erst ein Mal ein Problem mit einem Singleton, weil der Compiler den ersten Instanziierungsaufruf wegoptimiert hat und ein nebenläufiger Thread den ersten Zugriff gemacht hat. War, zugegebenermaßen, fies zu finden, weil das Problem erst im Release Build auftrat.

    Ach. Aber weisste was: das sind noch die harmloseren Probleme.



  • SeppJ schrieb:

    Du hast genau den Plan, weswegen Singletons so einen schlechten Ruf haben: Missbrauch als globale Variable. Du willst das Singleton nicht haben, weil dein Logger wirklich einzigartig sein müsste (Wieso sollte er das sein?), sondern weil du über die globale Funktion bequem an eine Loggerinstanz kommen möchtest.

    Ja, du hast recht. Es ist einfach die Erfahrung die fehlt so etwas *richtig* zu machen. Als Folge macht man dann dass, was man so findet oder wie es *scheinbar* alle machen. Ich hätte das Singleton tatsächlich als globale Variable missbraucht. Gefallen tut es mir allerdings auch nicht.

    Möglicherweise ist der Vererbungsansatz wie temi es beschrieben hat die beste Wahl. Ich denke ich werde es so umsetzen. ...oder spricht etwas dagegen?

    viele Grüße,
    SBond



  • Was willst du denn erreichen? Du willst einen Logger... aber das Problem, weshalb du an einen Singlton denkst, will sich mir nicht erschließen. Welches Problem sollte er nochmal lösen?



  • Artchi schrieb:

    DocShoe schrieb:

    Ich hatte erst ein Mal ein Problem mit einem Singleton, weil der Compiler den ersten Instanziierungsaufruf wegoptimiert hat und ein nebenläufiger Thread den ersten Zugriff gemacht hat. War, zugegebenermaßen, fies zu finden, weil das Problem erst im Release Build auftrat.

    Das ist mal ein sachlicher Hinweis. Sollte man im Hinterkopf behalten.
    Wie hast du das gelöst?

    Der Singleton sollte im Hauptthread der Anwendung erzeugt werden und erzeugt ein unsichtbares Kommunikationsfenster zur Synchronisierung mit dem Hauptthread der Anwendung (Windows). Der erste (Initialisierungs) Aufruf wurde wegoptimiert, weil er nur aus Seiteneffekten besteht. Die Lösung besteht darin, den Singleton auch wirklich zu benutzen.

    Singleton& instance()
    {
       static Singleton theSingleton;
       return theSingleton;
    }
    
    template<typename FuncType>
    void synchronize( FuncType Func )
    {
       instance().synchronize( Func );
    }
    
    int main()
    {
       // diesen Aufruf hat der Compiler wegoptimiert
       instance();
    
       // nicht wegoptimiert
       unsigned int ThreadID = instance().thread_id();
       if( ThreadID != ::GetCurrentThreadId() )
       {
          // darf nie passieren
       }
    }
    
    void some_thread_func()
    {
       // Aufruf aus nebenläufigem Thread
       synchronize( [] { cout << "Hello World"; } );
    }
    


  • Artchi schrieb:

    Was willst du denn erreichen? Du willst einen Logger... aber das Problem, weshalb du an einen Singlton denkst, will sich mir nicht erschließen. Welches Problem sollte er nochmal lösen?

    Naja ich möchte aus beliebigen Funktionen unterschiedlicher Objekte z.B. eine Log-Funktion aufrufen. Bin dann irgendwann auf einen Singleton gestoßen und war mir nicht sicher, ob es das richtige dafür ist. Scheint aber nicht zu sein. Nun werde ich es wohl über Vererbung lösen. ...ist subjektiv empfunden wesentlich besser.
    Wie gesagt... die Entscheidung auf Singleton fiel nur Aufgrund meiner Erfahrungslosigkeit.

    @all: Danke für eure Hilfe 🙂



  • Du willst unterschiedliche Logger-Typen haben, und du weißt zur Laufzeit nicht, welchen Typ bzw. willst das nicht konkret wissen?
    Fabrik-Methode wäre eine Möglichkeit.
    Anstatt Circle, Rectangle usw. machste unterschiedliche Logger:
    http://www.kharchi.eu/?p=885



  • SBond schrieb:

    Naja ich möchte aus beliebigen Funktionen unterschiedlicher Objekte z.B. eine Log-Funktion aufrufen.

    Manchmal ist es durchaus OK irgendwo einfach printf reinzuschreiben. Oder ne statische/globale "theLogger" Variable zu verwenden. Nicht alles muss immer 100% flexibel sein. Was in deinem Fall angebracht ist, kann man aus der Ferne natürlich nicht bestimmen.

    SBond schrieb:

    Nun werde ich es wohl über Vererbung lösen. ...ist subjektiv empfunden wesentlich besser.

    Wo/wie soll Vererbung die hier weiterhelfen? Ich ahne Schreckliches...

    Davon abgesehen würde ich dir raten mal folgende Begriffe zu googeln und dich darüber schlau zu lesen:
    * Inversion of Control
    * Dependency Injection
    * Service Locator


Anmelden zum Antworten