Singleton vs unnamed namespace



  • Hi,

    Inspiriert zu diesem Thread hat mich dieser teilweise verwandte Thread http://www.c-plusplus.net/forum/285025
    Aber hier sticht mir noch ein Spezialfall besonders ins Auge.

    Diese unnamed namespaces schwirren mir ständig im Kopf rum, genau so wie freie Funktionen. Alles in Klassen zu packen hat Java-Konsequenz, die aber in C++ keine Anwendung findet. Das habe ich erst kürzlich gelernt und mein Stil hat das alles andere als gefressen und erst Recht habe ich daraus noch kein allgemeines Procedere für mich abgeleitet.

    Folgende Aspekte scheinen für die Wahl von freien Funktionen/unnamed Namespace/Singleton oder direkt Klasse wichtig zu sein:

    1. Soll die Klasse überhaupt mehrfach instanziiert werden?
    2. Gibt es die Notwendigkeit etwas zu kapseln?
    3. Möchte man Vererbung (evtl. i.V.m. Polymorphie) nutzen?

    Hm, eigentlich reicht das schon fast, oder?

    Wenn man mehrere Instanzen braucht, machen gewöhnliche Klassen schon automatisch Sinn.

    Doch wenn nicht, machen Singletons für mich aber gerade überhaupt keinen Sinn mehr in C++. Man nutzt diese, wenn man global nur eine einzige Instanz der Klasse haben möchte. Doch sollte es dann überhaupt noch eine Klasse sein, wozu?

    Wenn man etwas kapseln möchte, kann man das auch in den unnamed namespace schieben. Problematisch ist natürlich, wenn man so lustige Konzepte wie Laufzeit- oder statische Polymorphie nutzen möchte. Aber das macht für Singletons sowieso keinen Sinn, richtig? Sonst hätte man für jede Kombination aus Templateparametern ein neues Singleton. Da fällt mir auf Anhieb kein Fall ein, wo das Sinn macht.

    Und Vererbung? Durch die Vererbung von Singletons erstellt man automatisch neue Klassen und damit Instanzen. Das halte ich auch nur begrenzt für sinnvoll.

    Die nächste Frage ist dann, ob unnamed namespaces irgendwelche Nachteile ggü. Singleton-Klassen haben, gerade was die Benutzung angeht. Gut ist, dass man keine lästige Zugriffsmethode mehr braucht, um die einzige Instanz zu erhalten. Außerdem wird keinem Benutzer vorgegaukelt, eine Mehrfachinstanziierung sei möglich, da es eben keine Klasse mehr ist.

    Nachteil wäre, dass man freie Funktionen aufrufen muss. Wenn man als Programmierer z.B. schon ein Objekt im Sinn hat wie irgendeine zentrale Verwaltungseinheit, dann ist natürlich ein Aufruf:

    CentralManagementUnit.AcquireVeryBigDonuts();
    

    intuitiv. Oder sollte es den Benutzer nicht interessieren, dass so eine Verwaltungseinheit irgendwo rumliegt, sondern einfach schreiben, was er möchte?

    acquireVeryBigDonuts();
    

    Dann gäbe es noch die Möglichkeit eines benannten namespaces:

    // Header
    namespace CentralManagementUnit
    {
        // Irgendwelche Funktionen
    };
    
    // C++
    
    namespace
    {
        // Gekapselte Attribute, private Funktionen etc.
    }
    
    namespace CentralManagementUnit
    {
        // Funktionsimplementierungen
    }
    

    Was sollte man jetzt wofür nehmen und wieso? Habt ihr da Favoriten, gibt es überwältigende Argumente, hat man in euren Augen keine Unterschiede zwischen Singleton und unnamed namespace oder was denkt ihr sonst zu dem Thema?

    Discuss! 🙂



  • Eisflamme schrieb:

    Doch wenn nicht, machen Singletons für mich aber gerade überhaupt keinen Sinn mehr in C++.

    Singletons können den Erzeugungszeitpunkt hinauszögern. Dann wartet man z.B. 500ms beim ersten Aufruf des Drucken-Dialogs und nicht wie beim Acrobat-Reader 30 Sekunden bei Programmstart auf alle Singletons auf einmal.



  • Du kannst ein Singleton mittels freier Funktionen implementieren. Wer sagt denn, dass ein Singleton immer mit singleton::get_instance() arbeiten muss?



  • Hm. Ok. Mehr Gedanken habt ihr nicht dazu? Das Thema las ich bisher nicht umfassend diskutiert, ist meine Frage so trivial? 😞



  • Eisflamme schrieb:

    Hm. Ok. Mehr Gedanken habt ihr nicht dazu? Das Thema las ich bisher nicht umfassend diskutiert, ist meine Frage so trivial? 😞

    Ich weiß nicht, was hier anders ist als im anderen Thread. Deine Frage war für mich nicht konkreter, sondern schwammiger.



  • Das andere Thema war "namespace vs Klassen" und hier bin ich "Singleton vs unnamed namespaces", rein vom Titel her bin ich hier schon konkreter. Konkret ist meine Frage nicht, aber ich hätte gedacht, da haben viele Gedanken zu von wegen:

    Jaa, also ich mache das immer so, dass ich Singletons fast nie als Klassen benutze, sondern immer namespaces nutze, außer [...]. Und sonst haben unnamed namespaces halt den Vorteil, dass [...], wohingegen...

    Aber anscheinend gibt es zu dem Thema nix in der Richtung, schade.



  • Jaa, also ich mache das immer so, dass ich Singletons fast nie benutze und globale Variablen (auch in unnamed namespaces) auch nicht.

    Mußt einen Uhrumlauf abwarten vor einem Fazit. Viele sind jetzt in der Schule oder arbeiten und dürfen gar nicht mitreden.



  • Und wenn man ein Objekt nur einmal braucht, sodass freie Funktionen sich anbieten würde, es aber etwas gibt, dass man kapseln möchte? Entweder nimmt man eine Singleton-Klasse oder eine globale Variable in einem unnamed namespace.



  • Eisflamme schrieb:

    Und wenn man ein Objekt nur einmal braucht, sodass freie Funktionen sich anbieten würde, es aber etwas gibt, dass man kapseln möchte? Entweder nimmt man eine Singleton-Klasse oder eine globale Variable in einem unnamed namespace.

    Ja, sehe ich auch so.

    Halt, manchmal erzeuge ich Einmal-Objekte auch in der main() und reiche sie zum Benutzer runter.
    Oder das klassische

    int main(){
       World w;
       return w.run();
    }
    


  • Und was benutzt Du dann von beiden Varianten lieber?

    Also verbietest Du Mehrfachinstanziierung von Einmal-Objekten nicht? Ich habe aber manchmal das Problem, dass ich Systemfunktionen aufrufe, die nicht mehrfach aufgerufen werden sollen, sodass Mehrfachinstanziierung also böse ist.



  • Eisflamme schrieb:

    Und was benutzt Du dann von beiden Varianten lieber?

    Das hängt ganz stark vom Problem ab, dass man hat. Es gibt ja gerade in C++ auch nicht die Singleton-Lösung, sondern diverse Geschmacksrichtungen, welche unterschiedliche Probleme lösen sollen.
    Insbesonderes zur Behandlung von Race Conditions bei der Singleton-Dekonstruktion gibt es ziemlich viele Ansätze, wobei Einer häßlicher als der Andere ist.
    Und je nachdem, was man für ein Singleton haben will, bietet es sich entweder an, dies über freie Funktionen oder über Klassen zu realisieren.



  • Eisflamme schrieb:

    Und was benutzt Du dann von beiden Varianten lieber?
    Also verbietest Du Mehrfachinstanziierung von Einmal-Objekten nicht?

    Normalerweise nicht. Im nächsten Programm will ich zwei Welten haben. Oder zwei Drucker.

    Eisflamme schrieb:

    Ich habe aber manchmal das Problem, dass ich Systemfunktionen aufrufe, die nicht mehrfach aufgerufen werden sollen, sodass Mehrfachinstanziierung also böse ist.

    Ja, dann muß ich es verbieten.
    Aber ich habe keine Faustregel, ob ich Singleton oder globale Variable im anonymen namesdpace bevorzuge. Wobei die beiden sogar dasselbe sind, wenn ich die freie ReadOnly-Zugriffsfunktion mitanbiete.
    Der wichtigste Unterschied ist wohl, ob ich eine static lokale Variable oder static globale Variable nehmen will. Ob ich dann

    CentralManagementUnit.AcquireVeryBigDonuts();
    

    oder

    CentralManagementUnitAcquireVeryBigDonuts();
    

    oder

    CentralManagementUnit::AcquireVeryBigDonuts();
    

    anbiete, hängt glaube ich von der aktuellen relativen Mondfeuchtigkeit ab. Da entwickelt sich bei mir kein absolute statement dazu, weil es so selten vorkommt, und ich jedes Jahr irgendwie anders drauf bin.
    Es gibt außerdem übliches Vorgehen: theApp ist global und nicht static in der main.cpp, theWorld lokal und nicht static in der main(), Hilfstabellen zum Speichermanager sind dort im anonymous namespace also global und static, der Systemdrucker ist ein Meyers-Singleton also lokal und static, die Winsock-DLL hat die Tendenz dazu, im anonymous namespace der Socket.cpp zu liegen also lokal und static, kann aber auch durchgereicht werden also lokal und nicht static.



  • Eisflamme schrieb:

    Und wenn man ein Objekt nur einmal braucht [...]

    Wenn man nur ein Objekt eines Typs braucht bedeutet das noch lange nicht dass das Singleton Pattern angebracht ist. Leider ist das nur wie die meisten das Singleton Pattern einsetzen. Das ist aber schlicht und einfach falsch. Der Zweck des Singleton Pattern ist es um jeden Preis sicherzustellen dass in keinem Fall niemals und überhaupt mehr als ein Objekt eines Typs erzeugt wird. Mit dem Singleton Pattern drückst du aus dass es eine Eigenschaft des Typs ist dass er nur einmal instanziert werden kann. Die Tatsache dass du beim Singleton üblicherweise einen globalen Zugriffspunkt auf die eine Instanz hast ist lediglich eine Folge aber nicht der Zweck des Singleton Pattern.



  • volkard schrieb:

    Eisflamme schrieb:

    Und was benutzt Du dann von beiden Varianten lieber?
    Also verbietest Du Mehrfachinstanziierung von Einmal-Objekten nicht?

    Normalerweise nicht. Im nächsten Programm will ich zwei Welten haben. Oder zwei Drucker.

    Muss sagen, das argument verstehe ich irgendwie nie so richtig. Normalerweise ist es einfacher ein Programm so zu schreiben, dass es von bestimmten Dingen nur eines gibt, weil man halt nur einen Fall prüfen muss. D.h. wenn ich mein Programm nur auf eine "Welt" auslege, kann ich eben nicht eine zweite einfach erstellen und das wird dann schon alles gutgehen. Entweder also programmierst du von Anfang an so das du x Welten erwartest, und machst dir falls du es nie braucht völlig unnötige Arbeit, oder aber du erlaubst es ein Objekt zu kopieren das logisch gesehen eben nicht kopiert werden darf.

    Ein Objekt das singleton ist mehrfach zu erlauben ist erstmal ne sehr einfach aufgabe.. Konstruktor public, getInstance oder so weg, dann variable global machen (oder aber auf dependency injection umstellen wenn man will). Aufrufe noch korrigieren und fertig. Dass das ganze mit einer dann tatsächlich zweiten Instanz des ehemaligen Singletons dann noch logisch funktioniert ist die interessantere und deutlich schwierigere frage. Wenn die beiden Welten nämlich tatsächlich vollständig unabhängig voneinander wären, könnte ich das programm ja einfach 2 mal starten. Da hat dot irgendwie recht, nur weil ich etwas nur einmal brauche ist es noch kein Grund für ein singleton.

    Vielleicht hab ich da eine etwas andere Philosophie, aber wenn ich erlaube das ein objekt kopiert wird/mehrfach erstellt wird dann will ich mir auch sicher sein dass es intuitiv und einwandfrei funktioniert. Auch wenn ich 3 Jahre später nachdem ich alles vergessen habe nochmal drüber schau. Aus demselben Grund mach ich auch operator= und copyctor private falls ich nach den aktuellen Anforderungen nicht klar entscheiden kann, was der eigentlich machen soll (und ich ihn noch eh nicht brauche).



  • kleiner Troll schrieb:

    Ein Objekt das singleton ist mehrfach zu erlauben ist erstmal ne sehr einfach aufgabe.. Konstruktor public, getInstance oder so weg, dann variable global machen (oder aber auf dependency injection umstellen wenn man will). Aufrufe noch korrigieren und fertig.

    Äh.
    Du hast sowas noch nie in einem grösseren Projekt gemacht, oder?
    Die Kleinigkeit "Aufrufe korrigieren" kann Tage dauern.



  • volkard

    theApp ist global und nicht static in der main.cpp, theWorld lokal und nicht static in der main(), Hilfstabellen zum Speichermanager sind dort im anonymous namespace also global und static, der Systemdrucker ist ein Meyers-Singleton also lokal und static, die Winsock-DLL hat die Tendenz dazu, im anonymous namespace der Socket.cpp zu liegen also lokal und static, kann aber auch durchgereicht werden also lokal und nicht static.

    Hmm... Wieso sind manche Dinge statisch und andere nicht? Global und lokal verstehe ich, das hat einfach mit den Zugriffsrechten zu tun bzw. gibt es für Kapselung halt lokal + Zugriffsmethode.

    Oder meinst Du ein anderes static als das mit dem static -Schlüsselwort? Und ich nehme an, mit global + nicht-static meinst Du nicht, dass das innerhalb der main()-Funktion deklariert ist, oder? Falls doch, inwiefern ist das denn global?

    dot:
    Die Punkte sind mir schon bewusst, danke. 🙂



  • Naja, was ist für dich "größer"?
    200k+ Zeilen? Nö hab ich zugegebenermaßen nicht.
    20k+ Zeilen? Jop.

    Sicher ist das nicht ne Sache von 5 Minuten, zumindest je nachdem wie tief das Singleton im Programm verankert ist.. Aber da es prinzipiell eine einfache Aufgabe ist muss man sich nicht so viele Sorgen um bugs die man einführen würde während dieser arbeit machen.

    "Tage" dauern würd ich für übertrieben halten,zumindest wenn man die variante mit der globalen variable nimmt.
    Kann man ja einfach nach namespace::GetInstance() suchen und das entsprechend ersetzen. Logisch muss man am code ja überhaupt nichts ändern.

    Schwieriger wirds natürlich wenn man auf dependency injection umstellt. Aber auch das seh ich als machbar an - vorallem als viel weniger arbeit als ein Riesenprogramm auf spezifikationen auszulegen, dies in 80% der Fälle nie brauchen wird.



  • kleiner Troll schrieb:

    volkard schrieb:

    Eisflamme schrieb:

    Und was benutzt Du dann von beiden Varianten lieber?
    Also verbietest Du Mehrfachinstanziierung von Einmal-Objekten nicht?

    Normalerweise nicht. Im nächsten Programm will ich zwei Welten haben. Oder zwei Drucker.

    Muss sagen, das argument verstehe ich irgendwie nie so richtig. Normalerweise ist es einfacher ein Programm so zu schreiben, dass es von bestimmten Dingen nur eines gibt, weil man halt nur einen Fall prüfen muss. D.h. wenn ich mein Programm nur auf eine "Welt" auslege, kann ich eben nicht eine zweite einfach erstellen und das wird dann schon alles gutgehen. Entweder also programmierst du von Anfang an so das du x Welten erwartest, und machst dir falls du es nie braucht völlig unnötige Arbeit, oder aber du erlaubst es ein Objekt zu kopieren das logisch gesehen eben nicht kopiert werden darf.

    Tut mir leid, aber ich programmiere zuerst ein Game mit nur einer Welt. Und denke dabei absolut nicht, daß mehrere Welten möglich wären. Und dann wenn ich es zum Multi-Player-Game ausbauen will, wäre ich verratzt, wenn ich die Welt-Klasse (ohne Not!) zum Singleton gemacht hätte.



  • kleiner Troll schrieb:

    Ein Objekt das singleton ist mehrfach zu erlauben ist erstmal ne sehr einfach aufgabe.. Konstruktor public, getInstance oder so weg, dann variable global machen (oder aber auf dependency injection umstellen wenn man will). Aufrufe noch korrigieren und fertig.

    Falsch.
    Weil es ein Singleton war, haben die Armeen und Häuser, die Dialoge und Buttons NICHT gespeichert, auf welche Welt sie wirken, denn es gibt ja nur eine.
    Gibt es auf einmal zwei Welten, muß alles neu angefaßt werden.
    Das Singleton-Versprechen ist so stark, das tut man nicht ohne Not mal dahinsagen. Jedenfalls ich nicht.

    kleiner Troll schrieb:

    Aus demselben Grund mach ich auch operator= und copyctor private falls ich nach den aktuellen Anforderungen nicht klar entscheiden kann, was der eigentlich machen soll (und ich ihn noch eh nicht brauche).

    So ich auch.



  • Eisflamme schrieb:

    Hmm... Wieso sind manche Dinge statisch und andere nicht?

    Bei den globalen: Aus Tradition, nur aus Tradition, ohne, daß das besonders sinnvoll wäre.


Anmelden zum Antworten