Logging mit oder ohne Singleton?
-
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:
-
Jedes Objekt das loggen will kriegt einen Zeiger auf den Logger.
+: nix Globales
-: Speicherverschwendung, lästig -
Globalen Logger
+: überall leicht zugreifbar
-: globale Variable -
Singleton
+: überall leicht zugreifbar
-: Im Grunde globale Variable. Probleme bei Multithreading?
Wie würdet ihr eine Logging Klasse implementieren?
-
-
Würde eine Sink Klasse als Strategie implementieren, so das man entweder garnix, in eine Datei, in eine DB oder zu cerr/cout loggen kann z.B.
Dann würde ich eine Klasse für das Logging implementieren, welche eine dieser Strategien jeweils als LogSink benutzt.
Dann gibts bei Alexandrescu dieses nette Singeltontemplate, mit dem man eine normale Klasse einen Singleton verwandeln kann, das würde ich einsetzen, um meine Loggingklasse als Singleton anzusprechen.
Somit ist die Loggingklasse zwar kein Singleton direkt, aber wird in der Methodik erstmal so verwendet. Später lässt sich dann noch ein Proxy oder ähnliches vor das Logging schalten, falls es mit threads probleme gäbe z.b. auch ein boost::signals2 interface.
phlox
-
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:
-
Jedes Objekt das loggen will kriegt einen Zeiger auf den Logger.
+: nix Globales
-: Speicherverschwendung, lästig -
Globalen Logger
+: überall leicht zugreifbar
-: globale Variable -
Singleton
+: überall leicht zugreifbar
-: Im Grunde globale Variable. Probleme bei Multithreading?
Wie würdet ihr eine Logging Klasse implementieren?
-
Speicherverschwendung? Das ist definitiv kein Argument. Es ist sicher "lästig" immer Zeiger übergeben zu müssen, aber Speicherverschwendung, eher nicht...
-
Das ist tatsächlich unschön
-
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 denkereturn 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 denkereturn 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
-
Empfehlenswert zu dem Thema:
http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
-
KasF schrieb:
Empfehlenswert zu dem Thema:
http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdfgenau 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_lockingHast 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.