Logging mit oder ohne Singleton?



  • Das Singleton selber müsste doch eigentlich threadsafe sein.
    Ich denke

    return ObjectRef;
    

    ist eine atomare Operation. Und mehr benötigt ein GetInstance() ja nicht. Man muss nur die Schreibzugriffe synchronisieren. Oder liege ich da falsch?



  • Caster schrieb:

    Das Singleton selber müsste doch eigentlich threadsafe sein.
    Ich denke

    return ObjectRef;
    

    ist eine atomare Operation. Und mehr benötigt ein GetInstance() ja nicht. Man muss nur die Schreibzugriffe synchronisieren. Oder liege ich da falsch?

    Normalerweise erzeugt GetInstande() das Objekt erst beim ersten Aufruf. Und wenn das zwei Thhreads gleichzeitug versuchen, kann man Pech haben und zwei Objekte erzeugen.



  • Naja - man kann sich das Leben aber auch schwerer machen als nötig. Für eine konkrete Log-Klasse würde ich der Einfachheit halber fordern, dass getInstance() einmal in main() aufgerufen wird. Da laufen noch keine Threads und das Objekt wird sicher erzeugt. Für ein allgemeines Singleton, also beispielsweise in einer Library, wäre das natürlich unschön, in einem einzelnen Projekt aber finde ich es Ok, sowas zu machen.

    Eine allgemeine Lösung, die mir gerade im Kopf herumspukt, wäre ein Helper-Objekt, dass ebenfalls einmal getInstance() aufruf, und von das man als globale Variablen anlegt. Etwa so:

    // --- Singleton.cpp ---
    
    namespace {
    
    class SingletonHelper {
    
    public:
       SingletonHelper {
          Singleton::getInstance();
       }
    };
    
    SingletonHelper singletonHelper;
    
    }  // End namespace
    

    Das Objekt singletonHelper würde initialisiert, wenn das Modul (also EXE oder DLL) vom OS geladen wird. Zu diesem Zeitpunkt gibt es nur den ersten Thread. Falls ein anderes Modul vor diesem hier geladen wird und falls dieses über eine globale Variable (oder so) schon das Singleton verwendet, ist singletonHelper nutzlos - schadet aber auch nicht weiter.

    Gibt es in euren Augen etwas, das gegen diese Technik spricht?

    Stefan.



  • DStefan schrieb:

    Gibt es in euren Augen etwas, das gegen diese Technik spricht?

    Es könnte höchstens Schwierigkeiten geben, wenn das Singleton von anderen globalen oder statischen Variablen abhängig ist (z.B. std::cerr ) und bereits von Anfang an etwas macht. Die Initialisierungsreihenfolge ist auch bei Objekten in der Standardbibliothek undefiniert, oder?

    Naja, meistens sollte das aber kein Problem darstellen.



  • Nexus schrieb:

    DStefan schrieb:

    Gibt es in euren Augen etwas, das gegen diese Technik spricht?

    Es könnte höchstens Schwierigkeiten geben, wenn das Singleton von anderen globalen oder statischen Variablen abhängig ist (z.B. std::cerr ) und bereits von Anfang an etwas macht. Die Initialisierungsreihenfolge ist auch bei Objekten in der Standardbibliothek undefiniert, oder?

    Naja, meistens sollte das aber kein Problem darstellen.

    Da stimme ich zu. Der Ctor des Singletons sollte also brav sein. Aber aus dieser Falle gibt es (laut Standard) ohnehin keinen Ausweg. Oder?

    Stefan.



  • DStefan schrieb:

    Der Ctor des Singletons sollte also brav sein. Aber aus dieser Falle gibt es (laut Standard) ohnehin keinen Ausweg. Oder?

    Der Standard-Workaround wäre eine statische Funktion, die innerhalb eine static -Variable definiert und zurückgibt. Beim ersten Aufruf wird das Objekt also mit Sicherheit initialisiert. Hier ist das wegen der angesprochenen Thread-Problematik vielleicht nicht ideal (vorausgesetzt, man will nichts riskieren und hat möglicherweise mehrere Zugriffe).



  • DStefan schrieb:

    Das Objekt singletonHelper würde initialisiert, wenn das Modul (also EXE oder DLL) vom OS geladen wird.

    Nö, das wird initialisiert, bevor die erste Funktion der TU, in der singletonHelper definiert wurde, ausgeführt wird. Man müsste also schon in allen TUs einen "singletonHelper" definieren...

    Zu diesem Zeitpunkt gibt es nur den ersten Thread.

    Garantiert dir keiner, ist aber ein sehr übliches Szenario.



  • hustbaer schrieb:

    DStefan schrieb:

    Das Objekt singletonHelper würde initialisiert, wenn das Modul (also EXE oder DLL) vom OS geladen wird.

    Nö, das wird initialisiert, bevor die erste Funktion der TU, in der singletonHelper definiert wurde, ausgeführt wird. Man müsste also schon in allen TUs einen "singletonHelper" definieren...

    Zu diesem Zeitpunkt gibt es nur den ersten Thread.

    Garantiert dir keiner, ist aber ein sehr übliches Szenario.

    Hmmmm - "TU" == "Translation Unit"? Also was man auch als "Object File" kennt?

    Dann wäre deine Aussage aber falsch, denn solche Dateien sind für sich ja gar nicht ausführbar. Stattdessen werden sie zu ausführbaren Dateien gebunden.

    Und kann man tatsächlich einen neuen Thread starten, bevor überhaupt main() augerufen wird? Ich habe das nie ausprobiert, aber es kommt mir irngendwie unwahrscheinlich vor.

    Stefan.



  • vor main() werden alle statischen Variablen aller objekte in undefinierter reihenfolge initialisiert. Was wenn jetzt einer der Konstruktoren dieser statischen Variablen auch gerne loggen möchte?

    Die getinstance()-Methode kann man einfach durch einen mutex schützen.
    Man hat auch kaum einen Geschwindigkeitsverlust: http://en.wikipedia.org/wiki/Double-checked_locking





  • KasF schrieb:

    Empfehlenswert zu dem Thema:
    http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

    genau das habe ich auch gerade gelesen nachdem ich mal auf den link geklickt habe den ich vorher blind reinkopiert habe...

    blöde sache.



  • DrGreenthumb schrieb:

    vor main() werden alle statischen Variablen aller objekte in undefinierter reihenfolge initialisiert. Was wenn jetzt einer der Konstruktoren dieser statischen Variablen auch gerne loggen möchte?

    Die getinstance()-Methode kann man einfach durch einen mutex schützen.
    Man hat auch kaum einen Geschwindigkeitsverlust: http://en.wikipedia.org/wiki/Double-checked_locking

    Hast du dich mal gefragt warum bei Wikipedia alle möglichen Programmiersprachen beim Singleton-Pattern aufgeführt wurden, aber kein C++ dabei ist?

    Man bewegt sich in jedem Fall auf sehr dünnen Eis und niemand kann dir garantieren, dass sich die Compiler in diesen Fällen gleich verhalten.

    Somit gehört auch das double-checked locking zu den Anti-Pattern.

    Und die Moral von der Geschicht, trau dem Singleton-Pattern nicht 🤡



  • DStefan schrieb:

    hustbaer schrieb:

    DStefan schrieb:

    Das Objekt singletonHelper würde initialisiert, wenn das Modul (also EXE oder DLL) vom OS geladen wird.

    Nö, das wird initialisiert, bevor die erste Funktion der TU, in der singletonHelper definiert wurde, ausgeführt wird. Man müsste also schon in allen TUs einen "singletonHelper" definieren...

    Zu diesem Zeitpunkt gibt es nur den ersten Thread.

    Garantiert dir keiner, ist aber ein sehr übliches Szenario.

    Hmmmm - "TU" == "Translation Unit"?

    ja

    Also was man auch als "Object File" kennt?

    nein, nicht ganz. eine translation unit ist eine übersetzungseinheit. das "object file" ist das ergebnis, der übersetzung einer solchen übersetzungseinheit.

    Dann wäre deine Aussage aber falsch, denn solche Dateien sind für sich ja gar nicht ausführbar. Stattdessen werden sie zu ausführbaren Dateien gebunden.

    ja sie werden zu ausführbaren dateien gebunden. meine aussage ist trotzdem richtig, steht so im c++ standard, sorry.
    die mir bekannten compiler machen es zwar so, dass alles gleich zu programmbeginn initialisiert wird, aber sie dürften es eben wie gesagt auch anders machen.

    bei statischen lokalen variablen beispielweise ist es ja sogar vorgeschrieben, dass diese erst dann initialisiert werden, wenn die funktion in der sie definiert sind, das erste mal ausgeführt wird. genauer: wenn die "zeile mit der definition" ausgeführt wird.
    ist im prinzip nicht viel was anderes.

    Und kann man tatsächlich einen neuen Thread starten, bevor überhaupt main() augerufen wird? Ich habe das nie ausprobiert, aber es kommt mir irngendwie unwahrscheinlich vor.

    natürlich kann man das. oder sagen wir so: es kann sein dass man es kann. kommt auf die implementierung an, also auf OS, compiler, linker, runtime-library. um das genauer zu erklären müsste ich jetzt zu weit ins detail gehen, und das würde zu lange dauern. du könntest aber einfach davon ausgehen dass ich weiss wovon ich rede, und es mir glauben. natürlich kannst du auch weiterhin davon ausgehen dass ich mist verzapfe.



  • hustbaer schrieb:

    Und kann man tatsächlich einen neuen Thread starten, bevor überhaupt main() augerufen wird? Ich habe das nie ausprobiert, aber es kommt mir irngendwie unwahrscheinlich vor.

    natürlich kann man das. oder sagen wir so: es kann sein dass man es kann. kommt auf die implementierung an, also auf OS, compiler, linker, runtime-library. um das genauer zu erklären müsste ich jetzt zu weit ins detail gehen, und das würde zu lange dauern. du könntest aber einfach davon ausgehen dass ich weiss wovon ich rede, und es mir glauben. natürlich kannst du auch weiterhin davon ausgehen dass ich mist verzapfe.

    Muss man sein Programm so schreiben, dass es auf allen OS dieser Welt sicher laufen könnte, wenn es sowieso nur auf einem (bei den meisten hier Windows) läuft? Und Windows startet soweit ich weiß nicht irgendeinen Teil meines Programms als Thread, wenn ich es ihm nicht sage.



  • dfgfdfhhd schrieb:

    hustbaer schrieb:

    Und kann man tatsächlich einen neuen Thread starten, bevor überhaupt main() augerufen wird? Ich habe das nie ausprobiert, aber es kommt mir irngendwie unwahrscheinlich vor.

    natürlich kann man das. oder sagen wir so: es kann sein dass man es kann. kommt auf die implementierung an, also auf OS, compiler, linker, runtime-library. um das genauer zu erklären müsste ich jetzt zu weit ins detail gehen, und das würde zu lange dauern. du könntest aber einfach davon ausgehen dass ich weiss wovon ich rede, und es mir glauben. natürlich kannst du auch weiterhin davon ausgehen dass ich mist verzapfe.

    Muss man sein Programm so schreiben, dass es auf allen OS dieser Welt sicher laufen könnte, wenn es sowieso nur auf einem (bei den meisten hier Windows) läuft? Und Windows startet soweit ich weiß nicht irgendeinen Teil meines Programms als Thread, wenn ich es ihm nicht sage.

    Was weiss ich schon was du musst?
    Ich habe auch geschrieben "garantiert dir keiner, ist aber ein sehr übliches Szenario" (in Bezug auf "es läuft nur ein Thread während der Init-Phase").



  • hustbaer schrieb:

    dfgfdfhhd schrieb:

    hustbaer schrieb:

    Und kann man tatsächlich einen neuen Thread starten, bevor überhaupt main() augerufen wird? Ich habe das nie ausprobiert, aber es kommt mir irngendwie unwahrscheinlich vor.

    natürlich kann man das. oder sagen wir so: es kann sein dass man es kann. kommt auf die implementierung an, also auf OS, compiler, linker, runtime-library. um das genauer zu erklären müsste ich jetzt zu weit ins detail gehen, und das würde zu lange dauern. du könntest aber einfach davon ausgehen dass ich weiss wovon ich rede, und es mir glauben. natürlich kannst du auch weiterhin davon ausgehen dass ich mist verzapfe.

    Muss man sein Programm so schreiben, dass es auf allen OS dieser Welt sicher laufen könnte, wenn es sowieso nur auf einem (bei den meisten hier Windows) läuft? Und Windows startet soweit ich weiß nicht irgendeinen Teil meines Programms als Thread, wenn ich es ihm nicht sage.

    Was weiss ich schon was du musst?

    Ich weiß, dass hier oft alles verkompliziert wird, nur weil einer irgend nen Spezialfall kennt (und damit angeben will), 1 Byte ist nicht immer 8 Bit usw... Die Fragen hier kommen doch zu 99% von Anfängern die vor ihrem Desktop PC sitzen. Wer an solchen spezial Rechnern sitzt wird sehr wahrscheinlich keine Anfängerfragen in nem C++ Forum stellen.



  • dfgfdfhhd schrieb:

    Ich weiß, dass hier oft alles verkompliziert wird, nur weil einer irgend nen Spezialfall kennt (und damit angeben will), 1 Byte ist nicht immer 8 Bit usw... Die Fragen hier kommen doch zu 99% von Anfängern die vor ihrem Desktop PC sitzen. Wer an solchen spezial Rechnern sitzt wird sehr wahrscheinlich keine Anfängerfragen in nem C++ Forum stellen.

    Dass jemand angeben will, wenn er auf einen Spezialfall hinweist (oder so) ist eine ziemlich fiese Unterstellung! Wie gut, dass du hier anonym postest...

    Mag sein, dass der weitaus größte Teil der Fragen hier von Anfängern gestellt werden, aber das bedeutet nicht, dass es sich um ein Anfänger-Forum handelt. Bist du wirklich der Meinung, alle User hier sollten sich auf ein Niveau begeben, das Anfängern angenehm ist?! Und von wem würden dann die Anfänger etwas lernen?

    Bei einem Teil der Diskussion hier ging es nicht um Spezialfälle (zumindest nicht hauptsächlich), sondern darum, was der Standard definiert und was nicht. Solche Dinge zu wissen, ist vielleicht für viele hier nicht wichtig, die nach dem Motto "Geht doch!" arbeiten. Für jemanden aber, der ernsthaft in C++ programmiert, ist es von großer Wichtigkeit, unterscheiden zu können, was der Standard definiert und was vielleicht nur mit dem gerade eingesetzten Compiler funktioniert. Nicht umsonst übersetzen viele Programmierer ihren (wichtigen) Code mit mehr als einem Compiler.

    Mir persönlich ist es sehr wichtig, hier von Leuten wie hustbaer darauf hingewiesen zu werden, wenn ich mich in bestimmten Punkten irre - oder eben von "Spezialfällen" ausgehe, ohne dass meine Annahmen im Standard definiert sind. Aus diesem Grunde lese ich hier mit, stelle eigene Fragen oder poste Ideen. Ich möchte mich verbessern und dazu lernen - obwohl ich ganz sicher kein Anfänger mehr bin.

    Also ärgere ich mich über Beiträge wie deinen. Und wünsche mir ein Bischen mehr Respekt. Falls du etwas nicht verstehst oder überflüssig findest, oder falls du der Meinung bist, jemand wolle nur angeben, blättre doch einfach weiter und suche einen Beitrag, der deinem Niveau angemessen ist.

    Teufel noch eins!

    Stefan.



  • DStefan schrieb:

    Und kann man tatsächlich einen neuen Thread starten, bevor überhaupt main() augerufen wird? Ich habe das nie ausprobiert, aber es kommt mir irngendwie unwahrscheinlich vor.

    Klar kann man. Ich hatte zum Beispiel mal die Nase voll von der lahmen Konsoleausgabe in WinXP. Also habe ich cout reimplementiert, daß ich in einen Bildschirmgroßen lokalen Puffer schreibe und nur alle 10ms und nur wenn neue Daten da sind den Puffer auf die Konsole herausschreibe. Wenn ich da ein cout brauche, lege ich es gewöhnlich global an. Und schwupps, ist der Rausschreib-Thread vor dr main gestartet.



  • volkard schrieb:

    DStefan schrieb:

    Und kann man tatsächlich einen neuen Thread starten, bevor überhaupt main() augerufen wird? Ich habe das nie ausprobiert, aber es kommt mir irngendwie unwahrscheinlich vor.

    Klar kann man. Ich hatte zum Beispiel mal die Nase voll von der lahmen Konsoleausgabe in WinXP. Also habe ich cout reimplementiert, daß ich in einen Bildschirmgroßen lokalen Puffer schreibe und nur alle 10ms und nur wenn neue Daten da sind den Puffer auf die Konsole herausschreibe. Wenn ich da ein cout brauche, lege ich es gewöhnlich global an. Und schwupps, ist der Rausschreib-Thread vor dr main gestartet.

    Erschüttert! 😉 😉
    (Und genau darum ist dieses Forum so gut.)

    Stefan.


Anmelden zum Antworten