Auf Garantie für Intialisierungsreihenfolge vertrauen?



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



  • Dieser Beitrag wurde gelöscht!


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



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

    @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

    Naja...

    Wenn du in bestimmten Komponenten/Funktionen nur Fehler loggst (üblich), dann wirst du beim Testen nicht unbedingt merken dass der Logger zu spät erzeugt wird. D.h. das passiert dann beim Kunden wenn ein Fehler auftritt. Jetzt darfst du dir aussuchen ob du an der Stelle lieber einen Crash hast oder eine fehlende Log-Message.

    Und wenn beides nicht akzeptabel ist gibt es eigentlich nur zwei Möglichkeiten:

    1. Du gibst den Logger explizit überall als Parameter mit.
    2. Du besorgst dir den Logger über eine Funktion die ihn auch erzeugen kann falls er noch nicht existiert. Das wird aber u.U. schwierig wenn das Logging-Zeugs extern konfiguriert werden kann. Denn dann müsstest du dazu ja erstmal die Config lesen & parsen. Und wenn beim Config-Lesen/Parsen Funktionen verwendet werden die wiederrum den Logger anfordern, dann beisst sich der Schwanz in den Hund.

    Ansonsten ist mMn. eine akzeptable Lösung dass man den Logger einfach so früh wie möglich erzeugt. z.B. gleich direkt in main() bevor man noch grossartig andere Dinge macht.
    Wenn man dazu die Config lesen muss, dann kann man die Config lesen & parsen und dann den Logger erzeugen. Dann verwirft man die Config und liest sie neu - damit sämtliche Fehlermeldungen die beim Lesen/Parsen der Config geloggt worden wären halt geloggt werden.

    Je nach Logging-Library kann man u.U. auch Log Messages erstmal nur im RAM puffern lassen. Und dann nach dem Lesen und Anwenden der Config die gepufferten Messages durchgehen und entsprechend rausschreiben lassen.

    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.

    Also std::cout ist jetzt nichts was ich als Vorbild oder Rechtfertigung für irgendwas heranziehen wollen würde.



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

    Wenn du in bestimmten Komponenten/Funktionen nur Fehler loggst (üblich), dann wirst du beim Testen nicht unbedingt merken dass der Logger zu spät erzeugt wird. D.h. das passiert dann beim Kunden wenn ein Fehler auftritt. Jetzt darfst du dir aussuchen ob du an der Stelle lieber einen Crash hast oder eine fehlende Log-Message.

    Das ergibt natürlich Sinn und sollte man wohl im Normalfall beachten. Wobei es in meinem Fall ja sogar bekannt wäre, wo genau der Code crasht. Im Konstruktor der Datenbank. Wenn ich da sage: Ich baue es genau deswegen so um, dass es bei nicht vorhanden sein nicht crasht ... da könnte ich wohl auch einfach die Zeile rauslöschen 🙂 Aber generell ist das natürlich keine schlechte Idee. Bei mir jetzt mittelmäßig relevant, da es ein Privat Projekt ist.

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

    Ansonsten ist mMn. eine akzeptable Lösung dass man den Logger einfach so früh wie möglich erzeugt. z.B. gleich direkt in main() bevor man noch grossartig andere Dinge macht.

    Das war auch meine Idee / mein Plan. Ursprünglich habe ich deswegen diese Zeile auch einfach in den Konstruktor meiner Application Klasse geschrieben. Was ich dabei halt eben genau nicht bedacht habe ist das ja noch vor dem Konstruktor die Konstruktoren aller member aufgerufen werden, wo ich eben schon logge möchte.
    Daher fallen mir nur 3 Ansätze ein, um diese Strategie zu fahren:

    • Wie du sagtest, den Logger in die main packen. Das finde ich aber per se auch nicht so schön, da ja der ganze Setup Kram in die Application Klasse sollte.
    • Meine anderen Konstruktruktoren davon befreien etwas loggen zu müssen. Dann müsste ich stattdessen irgendwelche init Methoden verwenden, auch nicht so toll.
    • Dafür sorgen, dass die Konstruktoren später aufgerufen werden, indem ich Pointer / Smartpointer als Member nutze. Geht auch, aber ob das eine valide Begründung dafür ist? Keine Ahnung, vermutlich eher nicht.

    So richtig überzeugen tut mich weder menine Ansätze hier, noch die robusteren wie direkt übergeben. Basierend auf den bisherigen Antworten, scheint es aber auch wohl nicht die perfekte Lösung zu geben.
    Aktuell würde ich dazu tendieren das ganze zu mixen:

    • Übergeben bei den probelematischen Klassen, wo es gleichzeitig noch recht einfach ist (Ist ja auch performanter)
    • Den Logger trotzdem möglichst früh initialisieren
    • Sollte ich den Logger in abgelegen Ecken meines Codes brauchen auf die registry zurückgreifen

    Weil du es grade schon mit Config angesprochen haben. Wie würde ich es mit dem "per Parameter übergeben" Ansatz schaffen die Config auszulesen? Ne statische Methode einführen in der man das dann macht?

    class Application {
       std::shared_ptr<spdlog::logger> logger = getLoggerFromStaticFunction();
       MyDatabaseObjekt myDatabaseObjekt = MyDatabaseObject(logger); 
    }
    

    Das Problem hatte ich tatsächlich auch schon anderer Stelle, wo meine Datenbank Klasse ein Config Objekt erwartet, was man nicht so direkt erzeugen konnte. Da habe ich dann auch ne statische Methode genutzt, um die Config für die Datenbankklasse zu erzeugen. Hatte mich allerdings schon da gefragt, ob das so der richtige Ansatz ist.

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

    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.

    Ich muss mich da auch noch mehr einlesen. Bisher habe ich es so verstanden, dass ich eh erstmal ein Interface brauche, um meinen FakeLogger davon erben zu lassen und ihn in den Code einzuschleusen. Das erscheint mir bei nicht selbst geschrieben Code eher schwierig (Wenn man nicht grade einen Wrapper um jede externe Lib schreiben will, was ich auch schon öters gelesen habe ... was mir aber eher weniger spaßig erscheint) Aber ich bin hier auch wie gesagt blutiger Anfänger und habe es vermutlich einfach noch nicht 100% gerafft. Ggf. mache ich dazu nochmal einen Thread auf 😃



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

    Weil du es grade schon mit Config angesprochen haben. Wie würde ich es mit dem "per Parameter übergeben" Ansatz schaffen die Config auszulesen?

    Kommt auf die Logging-Library an. AFAIK gibt es welche die Makros verwenden, wo man dann u.U. auch nullptr als Logger übergeben kann -- dann wird halt nichts geloggt. In dem Fall einfach die Config-Lese-Methode mit nullptr aufrufen.
    Ansonsten einen Dummy-Logger erzeugen der alle Log-Messages schluckt.

    Da habe ich dann auch ne statische Methode genutzt, um die Config für die Datenbankklasse zu erzeugen. Hatte mich allerdings schon da gefragt, ob das so der richtige Ansatz ist.

    Config würde ich von aussen mitgeben.
    Zum Auslesen kann gerne eine freie Funktion verwendet werden, aber diese würde ich ausserhalb aufrufen und dann die gelesene Config als Parameter mitgeben.



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

    Ich muss mich da auch noch mehr einlesen. Bisher habe ich es so verstanden, dass ich eh erstmal ein Interface brauche, um meinen FakeLogger davon erben zu lassen und ihn in den Code einzuschleusen.

    Wie man das konkret macht, ist eine andere Frage. Ich hab da eher dran gedacht, den Logger (der immer noch global wäre) im Test zu initialisieren. Das müsste mit jedem Framework irgendwie gehen. Du kannst ja immer konfigurieren, wo die Dateien hingeschrieben werden, wie rotiert wird usw. Genauso kann man oft solche Spielereien implementieren, wie in eine Datenbank zu loggen oder ähnliches. Dafür bietet das Framework irgendwelche Klassen, die z.B. in log4j appender heißen, und die kann man auch selber implementieren.



  • @Leon0402
    Ja.
    Als ich "Logger mocken" geschrieben habe, meinte ich damit nicht notwendigerweise das Ding zu mocken das in der Library die du verwendest als Logger bezeichnet wird. Kann leicht sein dass es für dieses Ding kein "Interface" gibt wo man einen Mock davon ableiten könnte.

    Aber es gibt in jeder Library etwas was sich darum kümmert die Log-Messages rauszuschreiben. Log-Appender, Log-Writer - wie auch immer es heisst. Und da gibt es dann quasi immer so ein Interface - weil's halt quasi immer verschiedene Output-Mechanismen gibt. Da kann man dann für Tests einen Log-Appender/Log-Writer/... implementieren der z.B. einfach in einen vector<string> schreibt. Und dann ggf. auch Assertions darauf machen. Bzw. viele Libraries bringen auch schon einen passenden Log-Appender/Log-Writer/... mit den man dann einfach verwenden kann.



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

    Daher fallen mir nur 3 Ansätze ein, um diese Strategie zu fahren:

    • Wie du sagtest, den Logger in die main packen. Das finde ich aber per se auch nicht so schön, da ja der ganze Setup Kram in die Application Klasse sollte.
    • Meine anderen Konstruktruktoren davon befreien etwas loggen zu müssen. Dann müsste ich stattdessen irgendwelche init Methoden verwenden, auch nicht so toll.
    • Dafür sorgen, dass die Konstruktoren später aufgerufen werden, indem ich Pointer / Smartpointer als Member nutze. Geht auch, aber ob das eine valide Begründung dafür ist? Keine Ahnung, vermutlich eher nicht.

    Es gibt weitere Möglichkeiten. Du initialisierst den Logger bereits vor main in dem Du eine statische Variable anlegst, die dessen Konstruktion triggert. Dann steht er garantiert vor den Konstruktion Deiner Klassen zur Verfügung. Man muss dann aber aufpassen, dass man nicht in die Problematik der statischen Initialisierungsproblematik rein läuft. Das könnte dann der Fall sein, wenn die Logger Library auch etwas in dieser Richtung macht.

    Die Alternative wäre es ein Meyers Singleton als Wrapper zu nutzen, so dass beim ersten Aufruf des Wrappers der Logger konstruiert wird. Nachteil dabei Meyers Singletons lassen sich nicht sinnvoll gesteuert dekonstruieren.


Anmelden zum Antworten