Auf Garantie für Intialisierungsreihenfolge vertrauen?



  • Hallo zusammen,

    ich habe eine Klasse Application.h, die als Starpunkt für die Applikation dient. Diese hat z.B. als Member ein Objekt einer Klasse, welche die Daten der Applikation verwaltet.

    Ich möchte in meiner Applikation nun loggen mit einer Library. Dazu muss ich zunächst eine Methode aufrufen, um einen Logger zu erstellen und kriege einen shared_ptr darauf zurück. Zusätzlich ist der Logger in einer globalen Registry, sodass man sich den von überall her ziehen kann (wenn auch etwas kostenaufwendiger). Das rumreichen des Loggers in alle Klassen erscheint mir mühselig, da man Logging im zweifelsfall ja überall braucht, deswegen wollte ich die global registry nehmen.

    Also habe ich alles so angelegt in der Application Klasse:

    class Application {
       std::shared_ptr<spdlog::logger> logger = spdlog::basic_logger_mt("basic_logger", "error.log");
       MyDatabaseObjekt myDatabaseObjekt = MyDatabaseObject(); 
    }
    

    Im Konstruktur von MyDataBaseObject() will ich jetzt loggen und hole mir den logger aus der Registry mit spdlog::get("basic_logger"). Auch das klappt.

    Nicht klappen tut es allerdings, wenn ich in Application.h die beiden Member vertausche, weil dann MyDatabaseObject zuerst initalisiert wird und somit im Konstruktor der Logger noch nicht aus der global registry geholt werden kann.

    Jetzt frage ich mich, ob das so toller Stil ist darauf zu bauen, dass Member tatsächlich in dieser Reihenfolge initialisiert wird. Es ist zwar alles defiiniertes Verhalten, sieht mir aber tendenziell sehr fehleranfällig aus.

    Spontan fallen mir aber auch nicht so richtig Alternativen ein:

    • Ich könnte den Logger woanders erzeugen (sprich in der main Funktion), aber gefallen tut mir das eig. nciht so. Die Application Klasse war ja grade dazu da die Sachen zu erzeugen.
    • Ich könnte MyDatabaseObjekt zu nem (smart) pointer machen. Aber das scheint mir doch eher ne schlechte Begründung zu sein?

    Andere Ideen? Oder ist das gar nicht so schlimm und gängige Praxis sich auf die Initialisierungsreihenfolge zu verlassen?



  • Es gibt eine einfache Lösung für das Problem Du machst aus dem Logger ein Meyers Singleton und dann ist es egal von wo aus er zuerst aufgerufen wird.

    Was meinst Du mit Registry?



  • @Leon0402 sagte in Auf Garantie für Intialisierungsreihenfolge vertrauen?:

    Jetzt frage ich mich, ob das so toller Stil ist darauf zu bauen, dass Member tatsächlich in dieser Reihenfolge initialisiert wird. Es ist zwar alles defiiniertes Verhalten, sieht mir aber tendenziell sehr fehleranfällig aus.

    Das finde ich jetzt eher unproblematisch. Es ist eine Klasse, die hast du an einer Stelle im Griff. Da kann man auch einfach einen Kommentar dazuschreiben.
    Ob das so toller Stil ist... Vielleicht etwas unschön, aber stell dir z.B. vor, du hast eine Datenbank wie Sqlite und einen Pfad. Das wäre ein ähnliches Konstrukt, den Pfad muss man zuerst initialisieren, und daran würde sich wohl keiner stören.



  • @john-0 sagte in Auf Garantie für Intialisierungsreihenfolge vertrauen?:

    Was meinst Du mit Registry?

    spdlog führt eine eigene Registry, in der man verschiedene Logger ablegen und abrufen kann.



  • @DocShoe sagte in Auf Garantie für Intialisierungsreihenfolge vertrauen?:

    spdlog

    Das sind mal wieder die Informationen die ich in Fragen vermisse.



  • @john-0 Mal abgesehen davon, dass es im Code steht, möchte ich ja nicht erst die halbe Lib erklären ... hab es hier für ne überflüssige Information gehalten 🙂 Ist klar, was ich mit global registry meinte?
    https://github.com/gabime/spdlog#create-stdoutstderr-logger-object (Sie Kommentar im Beispiel)

    Ein Singelton darum zu basteln, scheint mir dann doch eher seltsam.

    @Mechanics Das Problem ist, dass für mich die Member Reihenfolge in der Klasse eigentlich eher so ne Stil Sache ist. Vlt. sortiert jemand die Sachen nach Alphabet, Funktion etc. ... wenn plötzlich A vor B stehen muss und B vor C, dann ist das doch relativ fragil. Das mag vlt. auch nicht jedem bewusst sein, dass es da so ne festgeschriebene Reihenfolge gibt.

    Technich geht es auf jeden Fall und notfalls so. Aber wollte trotzdem mal fragen, ob ihr das genauso machen würdet oder ob es vlt. sauberer geht (z.B. Singelton ist ja per se eine Möglichkeit, die lib hat sich aber wohl für ein andereres Konstrukt entschieden).



  • @Leon0402 sagte in Auf Garantie für Intialisierungsreihenfolge vertrauen?:

    Das Problem ist, dass für mich die Member Reihenfolge in der Klasse eigentlich eher so ne Stil Sache ist. Vlt. sortiert jemand die Sachen nach Alphabet, Funktion etc.

    Das ist ja gerade keine Stil-Sache. Neben der Initialisierungsreihenfolge ändern man ja auch das Layout im Speicher! Ggf. gibt es dann anderes Padding etc. Member-Variablen alphabetisch zu sortieren ist eine sehr schlechte Idee. Wenn du dich davor schützen willst, dann gute Nacht!

    Wie die Funktionen sortiert sind, ist dagegen egal.

    Beispiel:

    struct Original { long a2; long b2; char a1; char b1; };
    struct Sorted { char a1; long a2; char b1; long b2; };
    

    hier ergibt sich (bei mir):

    sizeof(Original) == 24
    sizeof(Sorted) == 32
    

    Mal eben 1/3 mehr Speicher, nur weil jemand Member sortiert hat.



  • @wob

    Es gibt durchaus Gründe, Member zu sortieren. Alexandrescu hat in einem seiner Talks gezeigt, dass es performanter ist häufig benutzte Member nach vorn zu stellen. Find` leider grad den Link dazu auf YT nicht mehr.



  • @DocShoe sagte in Auf Garantie für Intialisierungsreihenfolge vertrauen?:

    @wob

    Es gibt durchaus Gründe, Member zu sortieren. Alexandrescu hat in einem seiner Talks gezeigt, dass es performanter ist häufig benutzte Member nach vorn zu stellen. Find` leider grad den Link dazu auf YT nicht mehr.

    Natürlich. Das war doch gerade, was ich sagen wollte! Die Reihenfolge ist wichtig! Es ging hier aber um "alphabetisch sortieren", nicht z.B. nach Größe (um Padding zu minimieren) oder nach Zugriff (um Cache zu optimieren).

    Vielleicht ist das nicht richtig rübergekommen. Was nicht gut ist: aus Stilgründen einfach die Member irgendwie umsortieren (z.B. alphabetisch). Es ist gerade keine Stil-Sache! Das habe ich doch auch so geschrieben und auch mein Beispiel hat das doch nochmal untermauert.

    Weil @Leon0402 ja gesagt hatte, die Sortierung sei eine Stil-Sache und er wolle Code haben, der unabhängig von der Sortierung funktioniert. Die Sortierung ist aber wichtig und darf eben nicht ohne Nachdenken umgeändert werden.



  • @wob
    Dann sieh doch mein Posting als Bestätigung und nicht als Kritik 😉



  • Hmm okay dann gibt es Anwendungsfälle in denen die Sortierung wichtig ist. Würdet ihr daher alle sagen, vollkommen okay wie ich das gemacht habe?

    Man könnte ja dann anders argumentieren, dass meine Art Sortierungen auf Grund von Performance einschränken.



  • In deinem Fall ist Performance total latte. Die Fälle, die wob und ich meinen, treten dann auf, wenn tausende von Datensätzen sortiert werden müssen und das Sortierkriterium speichertechnisch weit hinten im Objekt liegt. Ob dein Logger jetzt vor oder nach dem db-Zugriffsobjekt liegt spielt performancetechnisch überhaupt keine Rolle.



  • Mein Usecase ist tatsächlich meistens "RAM sparen" und nicht "Performance". Wobei man durch gesparten RAM insgesamt mehr Objekte in den Cache bekommt und dadurch oft auch Performance gewinnt.

    Und auch bei Serialisierung kann die Reihenfolge durchaus wichtig sein.

    Ist die Sortierungsgeschwindigkeit tatsächlich von der Position im Objekt abhängig? Denn wenn ich einen vector<Objekt> habe, sind die Kriterien der Objekte doch immer gleich weit voneinander entfernt. Bekommt das der Prozessor nicht optimiert? Müsste ich benchmarken, bevor ich dazu ne Aussage mache.



  • @Leon0402
    Übergib den Logger als Parameter an MyDatabaseObjekt. Dann ist die Abhängigkeit klar und nicht mehr hinter einer globalen Registry versteckt.

    Oder ist das gar nicht so schlimm und gängige Praxis sich auf die Initialisierungsreihenfolge zu verlassen?

    Es ist nicht schlimm wenn die Abhängigkeit sichtbar ist. Wenn sie nicht sichtbar ist ist es schon eher schlimm. Wenn das vielfach in einem grösseren Programm vorkommt dann wird Refactoring dadurch extrem mühsam und fehleranfällig.



  • This post is deleted!


  • @Leon0402 sagte in Auf Garantie für Intialisierungsreihenfolge vertrauen?:

    @john-0 Mal abgesehen davon, dass es im Code steht,

    Wo steht das im Code? Da steht nur eine Aufruf mit einem bestimmten Namen, wenn man jetzt nicht die Library kennt, die Du an dieser Stelle verwendest, kann man nicht wissen, dass das nicht selbst geschrieben ist.

    Das Meyers Singleton wäre bei selbstgeschriebenem Code aber die Lösung das Initialisierungsproblem aus der Welt zu schaffen.



  • @hustbaer Und wenn es mehrere Klassen werden, die den Logger benötigen? Würdest du da wirklich überall den Logger rumreichen?

    Wobei das auch für Unit Tests vermutlich wesentlich einfacher wäre, es so explizit zu machen. Bei der registry wüsste ich jetzt auf Anhieb nicht wie man das mockt. (Mit Unit Tests beschäftige ich mich grade erstmals)



  • @Leon0402 sagte in Auf Garantie für Intialisierungsreihenfolge vertrauen?:

    @hustbaer Und wenn es mehrere Klassen werden, die den Logger benötigen? Würdest du da wirklich überall den Logger rumreichen?

    Logger ist da natürlich schon ein Spezialfall. Ich würde aber auf jeden Fall sagen: entweder muss das alles so gemacht sein dass nichts schlimmes passiert wenn der Logger nicht verfügbar ist (ausser dass evtl. die Log-Messages halt nicht geschrieben werden), oder der Logger sollte explizit übergeben werden.

    Logger über globale/statische Variablen verfügbar machen ist aber mMn. nicht grundsätzlich phöse. Es hat mMn. sogar einen nicht zu verachtenden Vorteil: der Logger kann überall "besorgt" werden ohne dass man gleich viel Code refactoren muss. D.h. man kann auch aus kleinen Utility-Funktionen oder -Klassen "einfach so" loggen. Der Code an dem ich aktuell arbeite hat keine globale Logger Registry, und das ist manchmal schon etwas lästig.

    Was Unit-Tests angeht kann einfach verbieten dass mehrere Unit-Tests im selben Prozess parallel laufen. Dann kann man den Logger immer noch "mocken" indem man halt der globalen Stelle (Logger-Registry oder was auch immer) den Mock unterjubelt bevor man den Test laufen lässt - und nachdem der Test fertig ist wieder entfernt. Parallele Ausführung von Unit-Tests muss dann halt mehrere Prozesse verwenden. Mit CTest geht das z.B. sehr einfach.



  • @hustbaer sagte in Auf Garantie für Intialisierungsreihenfolge vertrauen?:

    Logger ist da natürlich schon ein Spezialfall. Ich würde aber auf jeden Fall sagen: entweder muss das alles so gemacht sein dass nichts schlimmes passiert wenn der Logger nicht verfügbar ist (ausser dass evtl. die Log-Messages halt nicht geschrieben werden), oder der Logger sollte explizit übergeben werden.

    Das ist aber ja irgendwie noch schlimmer ... er würde dann nicht loggen und ich würde es nicht mal bemerken? Das der Logger nicht verfügbar ist, sollte ja i.d.R. nicht vorkommen

    @hustbaer sagte in Auf Garantie für Intialisierungsreihenfolge vertrauen?:

    Logger über globale/statische Variablen verfügbar machen ist aber mMn. nicht grundsätzlich phöse. Es hat mMn. sogar einen nicht zu verachtenden Vorteil: der Logger kann überall "besorgt" werden ohne dass man gleich viel Code refactoren muss. D.h. man kann auch aus kleinen Utility-Funktionen oder -Klassen "einfach so" loggen. Der Code an dem ich aktuell arbeite hat keine globale Logger Registry, und das ist manchmal schon etwas lästig.

    Ja, das sehe ich auch so. Das übergeben scheint mir mühselig hat man ja bei vergleichbarem auch nicht z.B. std::cout ... auch global.

    @hustbaer sagte in Auf Garantie für Intialisierungsreihenfolge vertrauen?:

    Dann kann man den Logger immer noch "mocken" indem man halt der globalen Stelle (Logger-Registry oder was auch immer) den Mock unterjubelt bevor man den Test laufen lässt

    Ah okay, ich wusste nicht inwiefern das möglich ist, das ist gut zu wissen.



  • Man könnte auch einen speziellen Log Appender mocken, der die Nachrichten annimmt, damit man sie im Test dann auswerten kann. z.B. könnte der Test erwarten, dass Log-Nachrichten kommen sollten oder eben nicht.
    Ob das mit spdlog geht, weiß ich nicht.


Log in to reply