Muss ich eine public-bool-Variable initialisieren? [gelöst]



  • Kommt drauf an... von statischen Variablen existiert nur eine Instanz, bei statischen member Variablen teilen sich alle Objekte der Klasse die statische Variable. D.h. wenn du mehrere Objekte eines Typs hast, und ein Objekt ändert den Wert der statischen Variablen von true auf false, dann ist der Wert für alle anderen Objekte ebenfalls false.
    Ob das deinem Design entgegenkommt weiß ich nicht, aber in der Regel führt die Faulheit von Programmierern oft zu seltsamen Effekten, die zu beseitigen weit mehr kosten als das ursprüngliche Problem vernünftig zu lösen.

    Gruß,
    Doc



  • Hallo

    Grundlagen C++?!

    static besagt das eine Member-Variable nur genau einmal für alle Klasseninstanzen vorhanden ist, nicht wie normal pro Instanz eine eigene.

    Was ist denn so schlimm daran, grundsätzliche alle Variablen zu initialisieren und sich nicht auf mögliche Standardinitialierungen zu verlassen?

    bis bald
    akari



  • akari schrieb:

    Grundlagen C++?!

    static besagt das eine Member-Variable nur genau einmal für alle Klasseninstanzen vorhanden ist, nicht wie normal pro Instanz eine eigene.

    Das war schon klar. Meine Frage nach Nachteilen zielt darauf ab, ob es weitere (nicht so bekannte) Faktoren gibt, die es bei der Verwendung von static zu beachten gilt!?!

    akari schrieb:

    Was ist denn so schlimm daran, grundsätzliche alle Variablen zu initialisieren und sich nicht auf mögliche Standardinitialierungen zu verlassen?

    Gegenfrage: Wenn es nur eine Instanz der Klasse in meinem Programm gibt und static-Variablen standardmäßig initialisiert werden, wieso sollte ich dann den Konstruktor so aussehen lassen:

    __fastcall TFormMain::TFormMain(TComponent* Owner)
    	: TForm(Owner)
    {
        Application->HintColor= clKunterbunt;
        Application->HintHidePause= kurz;
        Application->HintPause= 1000;
        bBlaBlub= false;
        bNewBla= false;
        bBlaPop= true;
        bBlauGelb= false;
        bConfused= false;
        bAFlag1= false;
        bAFlag2= false;
        bAFlag3= false;
        bAFlag4= false;
        bAFlag5= false;
        bAFlag6= false;
        bAFlag7= false;
        BlaNbr= "x";
        memset(DingDong, 0x00, 120);
        DingDong[60]= 0xBB;
        FastNr= "";
        if(Screen->Width>=1024)
        {
            Label->Left= ((Screen->Width/100)*50);
            Label->Top= ((Screen->Height/100)*50);
        }
        else
        {
            Label->Left= ((Screen->Width/100)*20);
            Label->Top= ((Screen->Height/100)*20);
        }
    }
    //-----------------------------------------------------------------------------
    

    wenn er auch so aussehen kann:

    __fastcall TFormMain::TFormMain(TComponent* Owner)
    	: TForm(Owner)
    {
        Application->HintColor= clKunterbunt;
        Application->HintHidePause= kurz;
        Application->HintPause= 1000;
        bBlaPop= true;
        BlaNbr= "x";
        memset(DingDong, 0x00, 120);
        DingDong[60]= 0xBB;
        FastNr= "";
        if(Screen->Width>=1024)
        {
            Label->Left= ((Screen->Width/100)*50);
            Label->Top= ((Screen->Height/100)*50);
        }
        else
        {
            Label->Left= ((Screen->Width/100)*20);
            Label->Top= ((Screen->Height/100)*20);
        }
    }
    //-----------------------------------------------------------------------------
    

    ???

    Verstehe nicht, was daran das Problem ist?



  • Hallo

    Eine Variable nur deshalb als static zu deklarieren um die Initialisierung zu sparen ist für schlechter Stil. Denn wie schon gesagt ist static ein eigenes Konzept.
    Für dich mag dein "Trick" noch nachvollziehbar sein, für jemand anders der deinen Code durchsehen muß nur ein lästiges Rätsel.

    Um den Konstruktor auszuräumen solltest du wie schon gesagt die einfachen Initialisierungen in die Initialisierungsliste verschieben.

    bis bald
    akari



  • Gut, dann lass ich die Initialisierungen im ctor und fertig! Ob ich jetzt 'ne Init-Methode schreib'
    (das ist doch mit Initialisierungsliste gemeint oder?) - spart auch keinen Code und macht das ganze nicht bequemer...

    Vielen Dank für Eure konstruktiven Antworten. 👍



  • Eine Init()-Methode ist damit nicht gemeint. Such mal nach Initialisierliste.



  • Initialisierungslisten sind Vorgaben, mit denen Variablen bei ihrer Erzeugung initialisiert werden:

    class MyClass
    {
       int Integer;
       double Double;
    
    public:
       MyClass() :
          Integer( 0 ),      // <-- Initialisierungsliste
          Double( 0.0 )      // <-- dito 
       {
       }
    
       MyClass( int iValue, double dValue )
       {
          // keine Initialisierungsliste !
          Integer = iValue;
          Double  = dValue;
       }
    };
    

    Eine zweistufige Initialisierung macht dann Sinn, wenn die Konstruktion eines Objektes teuer ist nicht bekannt ist, ob und wann auf Elemente der Klasse zugegriffen wird.

    Gruß,
    Doc



  • Moin Moin,

    weiter geht's:

    DocShoe schrieb:

    Eine zweistufige Initialisierung macht dann Sinn, wenn die Konstruktion eines Objektes teuer ist nicht bekannt ist, ob und wann auf Elemente der Klasse zugegriffen wird.

    Mit dem Satz kann ich mal garnix anfangen... und das nicht nur grammatikalisch! 😞

    So, den Konstruktor in der Main.cpp gibt's nun nicht mehr.

    Ein paar Sachen hab' ich in die OnShow-Methode des Forms verschoben:

    void __fastcall TFormMain::FormShow(TObjekt* Sender)
    {
        Application->HintColor= clKunterbunt;
        Application->HintHidePause= kurz;
        Application->HintPause= 1000;
        if(Screen->Width>=1024)
        {
            Label->Left= ((Screen->Width/100)*50);
            Label->Top= ((Screen->Height/100)*50);
        }
        else
        {
            Label->Left= ((Screen->Width/100)*20);
            Label->Top= ((Screen->Height/100)*20);
        }
    }
    //-----------------------------------------------------------------------------
    

    Und in der Header-Datei hat jetzt einen Konstruktor mit Initialisierungsliste und Rumpf, der so aussieht:

    class TFormMain : public TForm
    {
    __published:	// Komponenten, die von der IDE verwaltet werden
        //...
    private:	// Benutzerdeklarationen
        bool bFlagA, bFlagB;
        unsigned char ArrayA[520];
        HANDLE hTest;
        bool FunctionA(int);
        bool FunctionB(int, int);
        bool FunctionC();
        int  FunctionD();
        bool FunctionE(unsigned char, unsigned char, unsigned char, int);
    public:		// Benutzerdeklarationen
        bool bFlagC, bFlagD, bFlagE;
        unsigned char ArrayB[120];
        short sVarA;
        AnsiString StrA, StrB, StrC;
        bool FunctionF(int, int, int);
        bool FunctionG();
        bool FunctionH();
        bool FunctionI();
        bool FunctionJ();
        bool FunctionK(TObject *Sender);
        AnsiString FunctionL();
        AnsiString FunctionM();
        __fastcall TFormMain(TComponent* Owner): bFlagA(false),
                                                 bFlagB(false),
                                                 bFlagC(true),
                                                 bFlagD(false),
                                                 bFlagE(false),
                                                 StrA(""),
                                                 StrA("x")
    	{
    		memset(ArrayB, 0x00, 120);
    		ArrayB[60]= 0xBB;
    	}
    };
    

    Das müsste es ja sein, dachte ich... das war mein Fehler: ich habe gedacht! Jetzt bekomm' ich folgende Fehlermeldung beim Compilieren:

    [C++Fehler] Main.h(87): Cannot find default constructor to initialize base class 'Forms::TForm'.

    Was habe ich falsch gemacht?



  • Hallo

    Du sollst die Initialisierungsliste nicht ersetzen, sondern erweitern. Denn die Basisklasse TForm gehört als erstes dorthin, wo sie ja schon war.

    __fastcall TFormMain(TComponent* Owner): TForm(Owner), bFlagA(false),...
    

    Die Fehlermeldung besagt nichts weiter, als das der Compiler versucht mangels fehlender Angabe den parameterlosen Standardkonstruktor TForm() zu finden, den es aber nicht gibt.

    Und warum hast du die Implementation des Konstruktor nun im Header? Du hättest die besser in der cpp-Datei lassen sollen.

    bis bald
    akari



  • Der vom Builder eingesetzte Kontruktor initialisiert seine Basis mit TForm(Owner), was Du hier einfach rausgelöscht hast.



  • Ach ja, wie dumm TForm(Owner) rausgelöscht... Besagt ja eigentlich die Fehlermeldung... mit den Fehlermeldungen stell' ich mich manchmal noch n bissl an... schon korrigiert, Danke! 🙂

    akari schrieb:

    Und warum hast du die Implementation des Konstruktor nun im Header? Du hättest die besser in der cpp-Datei lassen sollen.

    Mir ist einfach nicht bewusst was daran schlecht ist... Kannst du dazu bitte etwas mehr sagen, ausser dass ich den Implementation des Konstruktors besser in der cpp gelassen hätte?



  • Hallo

    Das betrifft erstmal das Konzept Datenkapselung. Headerdateien sind Teile von Schnittstellen, dort sollte nur das nötigste stehen was von außen kommende Zugriffe brauchen. Alle Implementationsdetails gehören in die cpp-Dateien (wenn möglich).
    Das zweite ist einfach Praxis. Änderungen an Headerdateien erzwingen immer ein Neukompilieren alle davon abhängigen Übersetzungseinheiten (alle die die Headerdatei includen). Deshalb wirst bei bei großen Projekten froh sein, wenn du möglichst wenig in den Headerdateien stehen hast was geändert werden müßte.

    bis bald
    akari



  • Ok, cool - klingt nachvollziehbar. Implementation des ctor ist inzwischen in der cpp gelandet.

    Vielen Dank nochmal an Alle Beteiligten! 🙂



  • Hi Kolumbus,

    hab mir gerade das Posting durchgelesen, das du nicht verstanden hast. Muss mich wohl beim Umformulieren des Satzes etwas, äh, verheddert haben. Manchmal eine Grammatik wie ein griechischer Gemüsehändler ich habe, junger Padawan.

    Aber jetzt im Ernst:
    Wenn die Konstruktion eines Objektes teuer im Sinne von zeitaufwändig ist, z.B. wenn langwierige Berechnungen zur Bestimmung von Daten durchgeführt werden müssen, dann kann eine zweistufige Konstruktion durch eine private init() Methode Sinn machen. Angenommen eine Klasse hat komplexe, langwierig zu bestimmende, aber auch triviale Eigenschaften. Wenn man die komplexen Daten nicht braucht, macht es keinen Sinn, sie bereits im Konstruktor zu bestimmen, sondern erst beim ersten Zugriff auf diese Daten. Dieses Beispiel sollte das ganz gut veranschaulichen:

    class ComplexObject
    {
       ComplexData    Data_;
       unsigned int   TrivialInt_;
    
       bool           Initialized_;
    
    public:
       ComplexObject() : 
          TrivialInt_( 1 ),       // Integer mit 1 initialisieren
          Initialized_( false )   // komplexe Eigenschaft noch nicht initialisiert
       {
       }
    
       unsigned int get_int() const
       {
          // Zugriff auf triviale Eigenschaft
          return TrivialInt_;
       }
    
       ComplexData get_complex() const
       {
          // Zugriff auf komplexe Daten
          if( false == Initialized_ )
          {
             // Daten werden zum ersten Mal angefragt, Bestimmung durchführen
             init();
          }
          return Data_;
       }
    
    private:
       void init()
       {
          // führe Bestimmung der komplexen Eigenschaft durch
          // ...
    
          // Objekt jetzt vollständig initialisiert
          Initialized_ = true;
       }
    };
    

    Das Ganze firmiert unter lazy initialization, glaube ich.

    Gruß,
    Doc



  • DocShoe schrieb:

    Das Ganze firmiert unter lazy initialization, glaube ich.

    Oder auch als Virtual-Proxy-Pattern.



  • Da fällt mir noch was auf:

    Wofür werden die member ArrayA und ArrayB benutzt? std::vector bietet ungemein viele Vorteile gegenüber C-style arrays, insbesondere was das Überprüfen von gültigen Indizes oder die Übergabe an Funktionen betrifft.

    Ich setze C-style Arrays nur noch dann ein, wenn folgende Kriterien zutreffen:
    - ich benötige genau N Elemente
    - die Benutzung des Arrays ist lokal, es wird niemals als Funktionsparamter benutzt

    In deinem Beispiel könnte man

    class TestClass
    {
       unsigned char ArrayA[520];
       unsigned char ArrayB[120];
    
    public:
       TestClass()
       {
          memset( ArrayB, 0, 120 );
          ArrayB[60] = 0xBB;
       }
    };
    

    durch

    class TestClass
    {
       std::vector<unsigned int> ArrayA;
       std::vector<unsigned int> ArrayB;
    
    public:
       TestClass() :
          ArrayA( 520 ),
          ArrayB( 120, 0 )
       {
          ArrayB[60] = 0xBB;
       }
    };
    

    ersetzen.



  • DocShoe schrieb:

    [...] Muss mich wohl beim Umformulieren des Satzes etwas, äh, verheddert haben. Manchmal eine Grammatik wie ein griechischer Gemüsehändler ich habe, junger Padawan.

    😃 🤡 Wieder mal hat's den Kollegen gegenüber voll erwischt als ich losgeprustet habe... 👍 Ich werd' mal ein Handtuch ins unterste Schreibtischfach legen, für alle Fälle. 🤡

    Vielen Dank für die anschauliche Erklärung Doc, das leuchtet mir ein! 🙂

    witte schrieb:

    DocShoe schrieb:

    Das Ganze firmiert unter lazy initialization, glaube ich.

    Oder auch als Virtual-Proxy-Pattern.

    Also "zwei-/mehrstufige Initialisierung" kann ich mir besser merken. "lazy initialization" ist ja wenigstens noch so ausgedrückt, dass es das Vorgehen etwas beschreibt, aber "Virtual-Proxy-Pattern" ist mir irgendwie viel zu abstrakt... Oder wie kann ich die Worte in Zusammenhang zu dem von Doc beschriebenen Vorgehen bringen? 😕



  • O.K., von mir falsch ausgedrückt. Bei dem virtuellen Stellvertretermuster existieren zwei Klassen die denselben Typen implementieren. Die eine ist leicht und simuliert nur Basisfunktionalität, die andere die komplette.
    Wenn Du jetzt teuer im Sinne von Rechenzeit meinst, nimm' den Ansatz von _DocShoe_. Wenn Du aber teuer im Sinne von Speicherverbrauch meinst dann nimm dieses Muster. Beispiel: Textdokument laden: Erstmal Platzhalter für die Bilder generieren damit das Layout stimmt (nur Eigenschaften Höhe und Breite), und die kompletten Teile erst dann laden, wenn der Benutzer zu den entsprechenden Seiten blättert.



  • Ahhh 💡 klar witte, das Vorgehen ist natürlich von einem sehr ähnlichen Ansatz motiviert - verstehe! Also ist Virtual-Proxy-Pattern etwas Ähnliches, aber nicht dasselbe und deswegen konnte ich den Begriff nicht so richtig zuordnen. Alles klar. 👍

    DocShoe schrieb:

    Da fällt mir noch was auf:
    Wofür werden die member ArrayA und ArrayB benutzt? std::vector bietet ungemein viele Vorteile gegenüber C-style arrays, insbesondere was das Überprüfen von gültigen Indizes oder die Übergabe an Funktionen betrifft.
    Ich setze C-style Arrays nur noch dann ein, wenn folgende Kriterien zutreffen:
    - ich benötige genau N Elemente
    - die Benutzung des Arrays ist lokal, es wird niemals als Funktionsparamter benutzt [...]

    Beides ist bei Beiden Arrays der Fall. 😉 Trotzdem Danke für den Hinweis! Hast du absichtlich im std::vector-Beispiel unsigned int-Arrays genommen oder zufällig?



  • Ooops, Fehler meinerseits. Natürlich sollte das unsigned char heissen.

    Gruß,
    Doc


Anmelden zum Antworten