Frage zum Syntax für Ctor mit Initialisierung



  • Nunja, Du hast hier zwei Methodenkörper, einmal den leeren innerhalb der Klasse und einmal den mit der Zuweisung ausserhalb der Klasse. Lösung: Du wirfst einen davon weg. Du könntest z.B. die Initialisierungsliste in den Konstruktor ausserhalb der Klasse verfrachten.

    BTW: Du kannst die Zuweisung von m_nameB auch in die Initialisierungsliste verfrachten. Aber warum brauchst Du überhaupt in beiden Klassen je eine Kopie des Strings?

    ClassB::ClassB(std::string aName)
        : A(aName)
        , m_NameB(aName)
    {
    }
    

    EDIT:
    Zu allgemeinen Frage: Da hilft nur Sprachverständnis und Lesen der Fehlermeldung. In der Fehlermeldung steht ja, was falsch ist. Wenn man nun weiß, dass es nur einen Methodenkörper geben darf, und wie Methodenkörper und Initialisierungsliste auszusehen haben, ergibt sich die Lösung von selbst 😉

    Wobei ich zugeben muss, dass bei manchen Fehlern (vor allem wenn die Meldungen unterschiedlicher Compiler was völlig unterschiedliches aussagen) auch nur Erfahrung hilft.



  • Fellhuhn schrieb:

    ...Und m_NameB kann ebenso in die Initialisierungsliste, auch wenn ich die nicht mag. 😉

    Ändere kann mal "bitte" in "sollte" - besserer Programmierstil - unabhängig von deinen persönlichen Geschmack.



  • asc schrieb:

    Fellhuhn schrieb:

    ...Und m_NameB kann ebenso in die Initialisierungsliste, auch wenn ich die nicht mag. 😉

    Ändere kann mal "bitte" in "sollte" - besserer Programmierstil - unabhängig von deinen persönlichen Geschmack.

    Stil definiert immernoch jeder für sich selbst und ist eine Frage des persönlichen Geschmacks. :p



  • Keine Geschmacksfrage ist jedoch, dass die bisherige Variante zuerst einen leeren String erzeugt, der dann bei der Zuweisung durch eine Kopie eines anderen ersetzt wird. In der Initialisierungsliste würde direkt eine Kopie des Parameters erzeugt. 😉



  • Fellhuhn schrieb:

    Stil definiert immernoch jeder für sich selbst und ist eine Frage des persönlichen Geschmacks. :p

    Sofern etwas identisches Programmverhalten nach sich zieht mag das sein, ich bevorzuge z.B. die öffnenden und schließenden Klammern auf der Höhe des einleitenden Scopes, und Rücke innerhalb der Klammern ein. Hier gibt es unterschiedliche Programmierstile, die das Selbe machen.

    Persönlicher Geschmack sollte aber nicht zum tragen kommen wenn es ein unterschiedliches Verhalten nach sich zieht. In deinem Fall benötigt jede Membervariable zwangsweise einen Standardkonstrukor und Zuweisungsoperator falls du diesem Wert im Konstruktor noch zuweist (und dabei sogar noch einmal den Konstruktor aufrufst).

    // Unvollständiges Beispiel

    class A
    {
      public:
        A();
        A(long x);
    };
    
    class B
    {
      private:
        A a;
      B() // <-- A::A()
      {
        a = A(2); // A::A(long), A::operator=(const A&)
      }
    };
    
    class C
    {
      private:
        A a;
      C()
      : a(2) // A::A(long)
      {
      }
    };
    

    Jetzt sag mir bitte wo hier der persönliche Geschmack entscheidend ist. Letzteres ist alleine schon von den Verhalten der eindeutig bessere Programmierstil. Und nicht unbedingt eine Stilfrage des Geschmacks.

    cu André



  • Das würde bei mir dann eher unter schlecht aufgebaute Klasse und Unzulänglichkeit des Standards/Compilers fallen. Initialisierungslisten zerrupfen den ganzen Konstruktor und sorgen mE so für eine schlechtere Übersicht. Daher minimiere ich die Verwendung wo es nur geht. Gab bisher nie Probleme.



  • Die Möglichkeit, ein Objekt direkt mit Parametern zu initialisieren (statt mit einem Dummy-Inhalt, der sofort überschrieben wird) ist für Dich eine Unzulänglichkeit? Das ist aber eine interessante Argumentation 😉



  • Fellhuhn schrieb:

    Das würde bei mir dann eher unter schlecht aufgebaute Klasse und Unzulänglichkeit des Standards/Compilers fallen. Initialisierungslisten zerrupfen den ganzen Konstruktor und sorgen mE so für eine schlechtere Übersicht. Daher minimiere ich die Verwendung wo es nur geht. Gab bisher nie Probleme.

    Initialisierungslisten nicht zu benutzen ist aber ineffizient:
    Benutzt du die Initialisierungsliste, dann hast du fuer jede Membervariable 1 Ctor-Aufruf und gut ist. Benutzt du keine Initialisierungslisten, dann brauchst du nach dem Default-Ctor-Aufruf (der ja trotzdem aufgerufen wird) noch einen Aufruf, um das Objekt tatsaechlich zu initialisieren (in der Regel einen operator=). Das bedeutet meistens doppelt so viel Aufwand (der Default-Ctor und der operator= machen ja in der Regel das selbe: sie geben allen Member irgendwelche Werte).

    Ausserdem fuehrt das gern zu Designproblemen:
    Du verwendest keine Initialisierungslisten, also haben alle deine Objekte einen Ctor ohne Objekte. Damit du die Objekte dann initialisierst, haben alle deine Objekte wahrscheinlich sowas wie eine "init()"-Funktion, die eigentlich das macht, was der Ctor machen sollte. Das fuehrt dazu, dass deine Objekte nach dem erstellen noch nicht einsatzfaehig sind: wenn du irgendwann vergisst, init() aufzurufen, hast du ein Problem.



  • Für mich ist es eine Unzulänglichkeit das der Compiler dies nicht erkennt, also dort im Konstruktor unterscheidet.



  • Fellhuhn schrieb:

    Für mich ist es eine Unzulänglichkeit das der Compiler dies nicht erkennt, also dort im Konstruktor unterscheidet.

    Das kann der Compiler oft gar nicht, weil dein Ctor ja beliebig kompliziert sein soll? Wenn deine Klasse ein paar Member hat, dann muessen von allen Membern die Ctors aufgerufen werden, von diesen Members wieder die Ctors, und von diesen wieder, und und und und.... Du kannst vom Compiler nicht erwarten dass er so tief in die Vererbungshierarchie schaut und erkennt, dass keiner der Ctor-Aufrufe einen Effekt hat, bzw. der Effekt einige hundert/tausend Maschinenbefehle weiter wieder ueberschrieben wird, ohne dass zwischendrin auf die Ergebnisse zugegriffen wird.



  • Fellhuhn schrieb:

    Das würde bei mir dann eher unter schlecht aufgebaute Klasse und Unzulänglichkeit des Standards/Compilers fallen. Initialisierungslisten zerrupfen den ganzen Konstruktor und sorgen mE so für eine schlechtere Übersicht. Daher minimiere ich die Verwendung wo es nur geht. Gab bisher nie Probleme.

    Wo zerrupfen Initialisierungslisten bitteschön den Konstruktor? Es ist doch nur eine Aufzählung der einzelnen Member mit ihren Initialisierungslisten (Noch Übersichtlicher kann man es imho nicht machen). Ich glaube eher das dich die Schreibweise verstört.

    Zum zweiten: Ich habe sehr häufig den Fall das ich keinen Standardkonstruktor (und teilweise auch keinen Zuweisungsoperator/Kopierkonstruktor) definiere. Ein Objekt muss meines Erachtens immer einen konsistenten Stand aufweisen, nicht selten reichen Standardkonstruktoren dafür nicht aus. Ich sehe es daher eher als falsches Design der Klasse an wenn man dessen Benutzung vorschreibt. Und auf den Compiler/Standard kannst du das Problem auch nicht schieben, da du im Konstruktorrumpf selber schon erwartest das dieses Objekt vollständig existiert.

    Zu guter Letzt gibt es Fälle wo du an die Initialisierungsliste nicht vorbeikommst:
    a) Initialisierung von Instanzgebundenen Konstanten
    b) Verwendung von Objekten ohne Standardkonstrukor
    c) Verwendung von anderen als dem Standardkonstruktor der Basisklasse
    d) Übersicht und Exceptionsicherheit bei mehreren dynamisch allozierenden Membern (Dies geht mit Smartpointern sehr schön)

    cu André



  • Fellhuhn schrieb:

    Für mich ist es eine Unzulänglichkeit das der Compiler dies nicht erkennt, also dort im Konstruktor unterscheidet.

    Das tut er in meinen Augen aus gutem Grund nicht. Das einfachste Beispiel sind Referenzen. Der Compiler müsste hier unterscheiden, dass die erste Zuweisung die Referenz setzt, und jede weitere den operator= des dahinterliegenden Objektes aufruft. Daraus ergäben sich wesentlich mehr Probleme für die Compilerhersteller als mit der Initialisierungsliste.

    Man darf auch nicht vergessen, dass viele Sprachen ohne Initialisierungslisten wie z.B. Java mit Referenzen arbeiten, sprich es gibt hier keine uninitialisierten Objekte. Uninitialisierte Referenzen sind per Definition null und können damit auch belegt werden, nachdem bereits Anweisungen im Konstruktor durchgeführt wurden. C++ setzt voraus, dass zu Beginn des Konstruktor-Körpers bereits alle Membervariablen initialisiert sind.



  • asc schrieb:

    Wo zerrupfen Initialisierungslisten bitteschön den Konstruktor? Es ist doch nur eine Aufzählung der einzelnen Member mit ihren Initialisierungslisten (Noch Übersichtlicher kann man es imho nicht machen). Ich glaube eher das dich die Schreibweise verstört.

    Die Reihenfolge der Elemente in der Liste ist ja vorgegeben durch die Reihenfolge der Definition der Variablen in der Klasse. Daher sind hier Abhängigkeiten nicht immer aufzulösen (wenn sich diese je nach Konstruktor unterscheidet).
    Desweiteren sind komplexere Berechnungen im Konstruktor durchaus möglich die als Parameter an den Konstruktor von Membervariablen übergeben werden müssen. Das trennt das Ganze.
    Oder eben Memberpointer den Werte dynamisch zugewiesen werden. Finde ich doch sehr unschön.



  • Fellhuhn schrieb:

    ...Initialisierungslisten zerrupfen den ganzen Konstruktor und sorgen mE so für eine schlechtere Übersicht. Daher minimiere ich die Verwendung wo es nur geht. Gab bisher nie Probleme.

    Erst Zweiteres (also Dein Umgang mit Initialisierungslisten) führt zu Ersterem.

    Wenn Du Dir rechtzeitig angewöhnt hättest, Alles, was möglich ist, in Initialisierungslisten zu packen, hättest Du eine ganz klare und auch saubere Trennung zwischen Initialisierungen (in der gleichnamigen Liste) und sonstiger Fachlichkeit (im Ctor-Rumpf).

    Und was die Reihenfolge angeht, finde ich es sowieso besser, wenn Abhängigkeiten zwischen Membern (und genau DIE geben eine Reihenfolge in der InitListe vor) auch in der Klassendefinition auftauchen.
    Übrigens: Auch im Ctor-Rumpf selbst wirst Du diese Reihenfolge einhalten müssen...

    Gruß,

    Simon2.



  • e) Initialisierung von Referenzen



  • Simon2 schrieb:

    Übrigens: Auch im Ctor-Rumpf selbst wirst Du diese Reihenfolge einhalten müssen...

    Aber mit Initialisierungslisten kannst du nicht zwei Konstruktoren haben wo einmal A von B und einmal B von A abhängig ist. Ansonsten geht das ohne Probleme.

    Was den bisherigen Umgang angeht, so kann ich da nichts dran ändern. Denn ich erstelle keine neuen Projekte, sondern arbeite nur an bestehenden mit (sprich: auf der Arbeit). Zum privaten Programmieren komme ich schon seit Jahren nicht mehr.



  • Fellhuhn schrieb:

    Aber mit Initialisierungslisten kannst du nicht zwei Konstruktoren haben wo einmal A von B und einmal B von A abhängig ist. Ansonsten geht das ohne Probleme.

    Darf ich mal ein Beispiel sehen was du damit meinst? Nichts für ungut, aber zumeist gibt es immer eine vorgegebene "Navigationsrichtung" bei Objekten, und ggf. setzt man später noch beim einen Objekt einen Verweis auf das andere wenn gegenseitige Navigation nötig ist.

    Initialisierungslisten verwenden heißt nicht, das man den Konstruktorrumpf garnicht mehr verwendet (wenn gleich er bei mir meistens, aber nicht immer, leer ist).

    Fellhuhn schrieb:

    Was den bisherigen Umgang angeht, so kann ich da nichts dran ändern. Denn ich erstelle keine neuen Projekte, sondern arbeite nur an bestehenden mit (sprich: auf der Arbeit). Zum privaten Programmieren komme ich schon seit Jahren nicht mehr.

    Ich behaupte das nicht wenige von den Schreibern auch arbeiten, aber einmal geschriebener Code darf auch von Zeit zu Zeit angepasst und verbessert werden (Thema: Refactoring).

    cu André



  • asc schrieb:

    Darf ich mal ein Beispiel sehen was du damit meinst? Nichts für ungut, aber zumeist gibt es immer eine vorgegebene "Navigationsrichtung" bei Objekten, und ggf. setzt man später noch beim einen Objekt einen Verweis auf das andere wenn gegenseitige Navigation nötig ist.

    Zum Beispiel:

    class A{
    public:
      A(int a){ //... 
      }
      int calculateWhatever();
    };
    class B{
    public:
      B(int b){ // ... 
      }
      int calculateWhatever();
    };
    
    class C{
    public:
      C(){
        a = A(10);
        b = B(a.calculateWhatever());
      }
      C(int i){
        b = B(10);
        a = A(b.calculateWhatever());
      }
      A a;
      B b;
    
    };
    

    oder eben die Reihenfolge abhängig von einem Konstruktorparameter etc.

    Initialisierungslisten verwenden heißt nicht, das man den Konstruktorrumpf garnicht mehr verwendet (wenn gleich er bei mir meistens, aber nicht immer, leer ist).

    Eben. Und in dem Fall ist es getrennt, was ich sehr unschön finde.

    Ich behaupte das nicht wenige von den Schreibern auch arbeiten, aber einmal geschriebener Code darf auch von Zeit zu Zeit angepasst und verbessert werden (Thema: Refactoring).

    In dem Bereich in dem ich arbeite ist Refactoring per Anweisung verboten. Es ist eine Fehlerquelle die niemand bezahlt.



  • Das Beispiel sieht mir dann doch etwas arg konstruiert aus. Und selbst dann koennen noch 3 von den 4 Zuweisungen in die Initilaisierungsliste, wobei ich vielleicht auch einen trotzdem in den Rumpf setzen wuerde:

    class C{
    public:
      C() : a(10), b() {
        b = B(a.calculateWhatever()); //kann auch in initliste
      }
      C(int i) : a(), b(10) {
        a = A(b.calculateWhatever());
      }
      A a;
      B b;
    };
    

    Ich leg mir eigentlich immer eine vollstaendige Initialisierungsliste an, allein um sicher zu gehen, dass ich auch tatsaechlich alle Member initialisiere. Ich hasse es naemlich wenn irgendwelche Fehler auftauchen, nur weil ich was uebersehen hab und irgedein int-member mit komischen "zufalls"-Werten initialisiert wurde...
    Das setzen von b in C::C() habe ich hier deshalb in den Ctor-Rumof gezogen, weil in gewisser Hinsicht dort etwas mehr passiert als nur eine stumpfe initialisierung, naemlich etwas, das abhaengig ist von einem anderen Member, und das kann boese enden wenn da jemand nichtsahnend unten die Memberdeklarationen vertauscht.

    Fellhuhn schrieb:

    In dem Bereich in dem ich arbeite ist Refactoring per Anweisung verboten. Es ist eine Fehlerquelle die niemand bezahlt.

    Dann sehen eure Chefs das aber etwas sehr einseitig. ihr seid also per Weisung dazu verpflichtet, veralteten und eventuell fehldesignten Code mit all seinen Fehlern weiter zu verwenden (refactoring ist ja dazu da, sowas zu beheben)? Natuerlich kostet Refactoring Zeit und es schleichen sich Fehler ein, die erst debuggt werden sollten. Trotzdem stellt sich die Frage, ob das rumwerkeln mit Patchwork-Code, unuebersichtlichen Hacks und Zusaetzen nicht mehr Zeit (udn Nerven) kostet. Aber das scheint wohl Firmenpolitik zu sein...



  • Fellhuhn schrieb:

    In dem Bereich in dem ich arbeite ist Refactoring per Anweisung verboten. Es ist eine Fehlerquelle die niemand bezahlt.

    Ich finde gammeligen Code wo immer nur Flicken rumgeklebt werden eine viel größere Fehlerquelle (und im Endeffekt teuerer) als ein Code wo man auch noch umgestalltet um ihn besser lesen und warten zu können. Aber das ist Firmenentscheid.

    Alle meine bisherigen Erfahrungen haben diese Aussage bislang untermauert.

    Zu deinem Beispiel: Solche Fälle hatte ich bislang noch nicht, und zumindestens in meinen Bereich wüsste ich auch auf Anhieb keinen Anwendungsfall für sich ändernde Abhängigkeiten. Lösen würde ich es aber wohl durch eine Ebene der Indirektion, hängt natürlich auch von der Häufigkeit der Aufrufe ab, wobei man dem gegenüber auch die Kosten der zusätzlichen Konstruktionen und Zuweisungen in eurem Code gegenüber stellen muss.

    cu André


Anmelden zum Antworten