Auf Garantie für Intialisierungsreihenfolge vertrauen?



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



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

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

    Damit man die Klasse besser widerverwenden kann?

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

    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.

    Ah okay vielen Dank für den Hinweis, auch an @Mechanics.

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

    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.

    Ich glaube dann würde ich es eher in der main machen. Zumindest in meinem Fall hätte ich glaube ich von static nur Nachteile hier

    Was ist. eigentlich von dem Vorschlag zu halten, dass ich z.B. mein DatenbankObjekt Member zu einem (Smart) Pointer mache. Damit könnte ich dann einfach den Logger normal anlegen ... sogar im Konstruktor von main.cpp noch ein "Start Applikation" oder sowas loggen und dann gemütlich danach mein DatenbankObjekt erzeugen. Ich könnte hier dann sogar noch im Konstruktor vorher die Datenbank Config erstellen, die ich direkt übergeben kann (statt dann hier das wieder mit einer freien static Funktion zu machen -> Die Config sollte ja an das Objekt per Parameter übergeben werden und kann halt nicht direkt Konstruiert werden).
    Ist das ein valider Grund hier Pointer zu nutzen (um später initialisieren zu können)? Die dynamische Allokation erzeugt ja Overhead als Nachteil.



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

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

    Damit man die Klasse besser widerverwenden kann?

    Damit man in Unit-Tests eine von Hand zusammengebaute Config mitgeben kann.
    Und gleichzeitig wird die Klasse auch flexibler, ja.



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

    Die dynamische Allokation erzeugt ja Overhead als Nachteil.

    Beim Zugriff kann es Unterschied geben, der ist aber eher minimal .... aber schlimmer ist der Unterschied beim Erzeugen (Stack/Heap) . Aber wieviele Elemente/Pointer von der Sorte legst du an ? und wann, zur laufzeit in der 90% Schleife ?
    Wenn das einmal in ner Art initialisierung machst, wird es nicht auffallen 🙂 Da wäre mir "Design" wichtiger.

    Generell grad loggen ist ein Thema, wo Singletons mehr Vorteile bringen können als Nachteile 🙂
    Viele Log Frameworks sind um Singletons / statische variablen aufgebaut. Ich hätt da wenig Skrupel das ebenso zu machen.

    Allerdings ist auch das Verlassen auf die Reihenfolge in der Init-Liste keine Todsünde .... in anderen Fällen würde das auch manch kurze elgante Lösung verhindern, wenn man zu paranoid ist.

    Meine Preferenzen: da ich meist Logging Frameworks verwende, hab ich meist das singleton/static soweiso. Und kann dann die anderen Objekte in der main anlegen mit verfügbaren log.

    Ciao ...



  • Einmal zu Beginn wollte ich dynamisch Allokieren. So solls ausssehen:

    class Application {
    public: 
       Logger loger;
       std::unique_ptr<DatabaseClass> databaseObject; 
       std::unique_prt(SomeOtherClass> someOtherClass;
    }; 
    
    Application::Application() 
    : logger {Logger {}}, databaseObject {nullptr}, someOtherClass {nullptr} {
       logger.log("Application ha started"); 
    
       SomeDatabaseConfig config {}; 
       config.setSomething("value"); 
       
       this->databaseObjekt = make_unique<DatabaseClass>(config);
       this->someOtherObject = make_unique<SomeOtherClass>();
    }
    

    Das gefällt mir persönlich recht gut. Dann ist die Reihenfolge sehr eindeutig und ich kann setup wie das Anlegen der Config für die Datenbank auch im Konstruktor machen etc.



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

    Ich glaube dann würde ich es eher in der main machen. Zumindest in meinem Fall hätte ich glaube ich von static nur Nachteile hier

    Glauben heißt nicht wissen.

    Was ist. eigentlich von dem Vorschlag zu halten, dass ich z.B. mein DatenbankObjekt Member zu einem (Smart) Pointer mache.

    Es ist egal, ob Du da einen unique_ptr (an der Stelle sicherlich sinnvoller als ein shared_ptr) anlegst oder das Problem selbst im Datenbankobjekt löst.Wesentlich ist dann, dass das Objekt mit einem Zustand konstruiert wird, der ein nicht nutzbares Objekt erzeugt, und die Initialisierung verzögert erfolgt. Manchmal lässt sich so etwas nicht vermeiden.

    Damit könnte ich dann einfach den Logger normal anlegen ... sogar im Konstruktor von main.cpp noch ein "Start Applikation" oder sowas loggen und dann gemütlich danach mein DatenbankObjekt erzeugen.

    Ich nehme an, dass Du in der main Funktion ein Objekt anlegen willst, dass die notwendigen Initialisierungen von anderen Objekten durchführt. Das ist nichts anderes als einzige große globale Variable, die als Member alle anderen Dinge aufnimmt. Die Designfrage ist nun, ob Du diese globale Variable durchreichen willst, oder ob Du sie als Singleton umsetzt, so dass man sie nicht explizit durchreichen muss.

    Ist das ein valider Grund hier Pointer zu nutzen (um später initialisieren zu können)? Die dynamische Allokation erzeugt ja Overhead als Nachteil.

    Nein, das kann man so nicht stehen lassen. Allokationen kosten zwar Zeit, aber in Relation etwa zum Aufbau der DB-Verbindung ist das vernachlässigbar. Ein größeres Problem ist die Größe des Stacks, der ist nämlich relativ begrenzt, so dass man da nicht beliebig Objekte erzeugen kann. Deshalb sollte man entweder Objekte mit static Storage (auf x86 gibt es verschiedene Speichermodelle mit dem Standardmodell ist man auf 2GB static begrenzt) oder auf dem Heap erzeugen.



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

    Ich nehme an, dass Du in der main Funktion ein Objekt anlegen willst, dass die notwendigen Initialisierungen von anderen Objekten durchführt. Das ist nichts anderes als einzige große globale Variable, die als Member alle anderen Dinge aufnimmt. Die Designfrage ist nun, ob Du diese globale Variable durchreichen willst, oder ob Du sie als Singleton umsetzt, so dass man sie nicht explizit durchreichen muss.

    Ich meinte im Konstruktor von Application und nicht von main. Sorry hatte mich da verschrieben. So hatte ich es vor:

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

    Einmal zu Beginn wollte ich dynamisch Allokieren. So solls ausssehen:

    class Application {
    public: 
       Logger loger;
       std::unique_ptr<DatabaseClass> databaseObject; 
       std::unique_prt(SomeOtherClass> someOtherClass;
    }; 
    
    Application::Application() 
    : logger {Logger {}}, databaseObject {nullptr}, someOtherClass {nullptr} {
       logger.log("Application ha started"); 
    
       SomeDatabaseConfig config {}; 
       config.setSomething("value"); 
       
       this->databaseObjekt = make_unique<DatabaseClass>(config);
       this->someOtherObject = make_unique<SomeOtherClass>();
    }
    

    Das gefällt mir persönlich recht gut. Dann ist die Reihenfolge sehr eindeutig und ich kann setup wie das Anlegen der Config für die Datenbank auch im Konstruktor machen etc.

    Ich bin mir grade nicht sicher, ob das irgendwas an deiner Aussage ändert oder nicht 😃


Anmelden zum Antworten