Frage zum Syntax für Ctor mit Initialisierung



  • 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é



  • pumuckl schrieb:

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

    Sehe ich ähnlich... Naja, das hat mir Anfangs den Ruf eingebracht langsam zu sein, inzwischen hat aber der Chef festgestellt das ich im Endeffekt vielleicht doch der schnellste bin, da mein Code üblicherweise weniger Fehler und Wartung nach sich zieht. Ganz davon "verschwende" ich meine Zeit auch noch mit *igitt* Dokumentation was ja noch fast schlimmer ist ;p

    cu André



  • @pumuckl
    Und schon ist es getrennt, was mir eben nicht gefällt. Dazu noch einiges an Kommentaren und anderen Berechnungen und man verliert schnell den Überblick. Und gerade in Initialisierungslisten finde ich Kommentare doch sehr ... störend.

    Was das Refactoring angeht, so sehe ich das auch so. Nur kann ich natürlich auch die Argumentation der Führungsriege nachvollziehen. Schließlich bekommt man Geld für Features und Support. Und wenn es um Aufträge im siebenstelligen Bereich geht, dann wird man den Teufel tun und bewährte Frickelarbeit umschreiben.



  • Hätte nicht gedacht das ich hier so eine Diskussion lostretet.

    Mir war bewust, das die Initialisierungsliste das ist, was ich will. Aber ich brauche auch den ctor Körper. Genau wie Fellhuhn bin ich der Meinung das der nichts in der Deklaration zu suchen hat. Das du, lieber Fellhun soviel viele Gegenstimmen bekommen hast liegt meines Erachtens hauptsächlich daran, dass du es nicht richtig begründet hast, bzw. die Initialisierungsliste abgelehnt hast, obwohl sie in der Deklaration nur an falscher Stelle steht: Es ist guter Stil die Deklaration säuberlich von der Definition zu trennen. Da die Initialisierungsliste teil der Definition ist, gehört sie in den Definitionsteil. Das dies geht musste ich erst anhand eines Nebensatzes von LordJaxom lernen:

    LordJaxom schrieb:

    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.

    Vorher dachte ich die Initialisierungsliste kann nur in der Deklaration stehen und habe deshalb krampfhaft versucht den Body von dieser Liste zu trennen. Was sinnvoller weise nicht geht.

    LordJaxom schrieb:

    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?

    Das habe ich mich auch gefragt. Bei meinen eigentlichen Code benötige ich den String sogar an drei Stellen, da das Parent Objekt zwei Memberobjekte hat, die beide den String brauchen. Wäre das nicht so, könnte ich den String in Childobjekt ermitteln und es von dort den Parent zur Verfügung stellen. So geht das aber nicht und das die Childobjekte sich den String von ihren Parent holen ist nicht ohne weiteres möglich, da sie ja Kontextunabhängig arbeiten sollen, also nichts von ihren Parent wissen sollen.

    LordJaxom schrieb:

    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 😉

    Irgendwie beisst sich die Katze da selber in den Schwanz. Gute Syntax-Literatur zu haben wäre da schon hilfreich. So werde ich wohl noch öfters mit meinen dummen Fragen hier auftreffen. Schön zu wissen das hier große Diskussionsbereitschaft besteht.

    Danke an alle
    Bernd

    P.S. (out of topic):

    Fellhuhn schrieb:

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

    Das kann ich sehr nachfühlen. Mein Chef kennt zwar Refactoring überhaupt nicht, aber hier ist es so, das der Programmierer als die Fehlerquelle angesehen wird. Was dazu führte das es mein Privatvergnügen wurde, diese zu beseitigen. Statt mich um bessere Arbeitsorganisation zu kümmern, habe ich mich immer extensiver in die Arbeit gestürzt (Hobby und Beruf war eins). Nun bin ich mit mein BurnOut-Syndrom in der 4. Phase (1. Phase: Euphorie; 2. Phase: Stagnation; 3. Phase: Frustration; 4. Phase Resignation). Ab der zweiten Phase hätte ich begreifen können, dass etwas schief läuft. Ich bin aber wie auf Schienen in die Katastrophe hineingerast. Erst danach habe ich die Sinnlosigkeit meines Handelns begriffen (und zwar für beide Seiten, den mein Chef wollte nicht, das ich ausbrenne). MACHT NICHT DEN SELBEN FEHLER! Es gibt so viele schöne Dinge die man in seinen Leben tun sollte, solange man sie noch tun kann, darum: Wenn Feierabend ist, einfach PC abschalten, Gedanken umschalten (Familie, Freunde, Sport, Outdoor Hobbys etc.) und gehen..

    Googelt mal den Begriff BurnOut-Syndrom. Da gibt es Tests im Internet. Wenn da mehr als 80% der Punkte auf einen von euch zutreffen, dann ist es höchste Zeit die Notbremse zu ziehen. Ich schreibe das hier, weil ich glaube das unter Informatiker ein großes Gefährdungspotential besteht, besonders wenn keine Familie da ist, die ein auf andere Gedanken bringt. Kinder sind da ideal. Natürlich nur, wenn man sie auch annimmt, statt sie nur irgendwo abzustellen.

    Ok, muss weiter...


Anmelden zum Antworten