Logging mit oder ohne Singleton?



  • Ich würde ein einfaches Singleton verwenden, das wie ein std::ostream verwendbar ist. Falls es ein Fehlerlog sein soll, auf dem Heap anlegen und nirgendwo mehr löschen.

    Falls das Programm multithreaded ist (und falls mehr als ein Thread in dasselbe Log schreiben könnte), würde ich die Klasse unbedingt entsprechend designen. Also eine Mutex verwenden und vor jede Ausgabe die Thread-Id schreiben. Andernfalls wird dein schönes Log, wenn du es wirklich mal brauchst leider nur Kraut und Rüben sein.

    Stefan.



  • DStefan schrieb:

    Ich würde ein einfaches Singleton verwenden, das wie ein std::ostream verwendbar ist. Falls es ein Fehlerlog sein soll, auf dem Heap anlegen und nirgendwo mehr löschen.

    Falls das Programm multithreaded ist (und falls mehr als ein Thread in dasselbe Log schreiben könnte), würde ich die Klasse unbedingt entsprechend designen. Also eine Mutex verwenden und vor jede Ausgabe die Thread-Id schreiben. Andernfalls wird dein schönes Log, wenn du es wirklich mal brauchst leider nur Kraut und Rüben sein.

    Stefan.

    Der Knackpunkt ist jedoch auch die statische Funktion getInstance()

    Das Problem ist etwas komplexer, als es scheint.



  • sariy schrieb:

    Hallo,

    ich brauche in meinem Code in vielen, vielen Klassen Logging.
    Also irgendwie sowas: logger->log(L_INFO, "foo bar");

    Nun frage ich mich, wie ich das am besten implementiere. So wie ich das sehe habe ich 3 Möglichkeiten:

    1. Jedes Objekt das loggen will kriegt einen Zeiger auf den Logger.
      +: nix Globales
      -: Speicherverschwendung, lästig

    2. Globalen Logger
      +: überall leicht zugreifbar
      -: globale Variable

    3. Singleton
      +: überall leicht zugreifbar
      -: Im Grunde globale Variable. Probleme bei Multithreading?

    Wie würdet ihr eine Logging Klasse implementieren?

    1. Speicherverschwendung? Das ist definitiv kein Argument. Es ist sicher "lästig" immer Zeiger übergeben zu müssen, aber Speicherverschwendung, eher nicht...

    2. Das ist tatsächlich unschön

    3. Multithreading bereitet bei Singleton tatsächlich große Probleme. Deshalb nennt man es hin und wieder auch ein Anti-Pattern 😉 Es gibt keinen effizienten und compilerunbhängigen Weg.



  • 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.


Anmelden zum Antworten