Frage zum Syntax für Ctor mit Initialisierung
-
Hallo,
ich habe leider noch massive Probleme mit den C++ Syntax. Heute scheitere ich am folgenden Problem:
Eine Klasse ClassB hat eine Memberklasse ClassA die kein Standard-ctor hat. Der ctor für ClassB braucht also die Parameter für ClassA um dessen ctor Aufrufen zu können. Mein Versuch:#include <string> class ClassA { public: ClassA(std::string aName); //protected: std::string m_NameA; }; class ClassB { public: ClassB(std::string aName) : A(aName) {}; protected: ClassA A; std::string m_NameB; }; ClassA::ClassA(std::string aName) { m_NameA = aName; } ClassB::ClassB(std::string aName) { m_NameB = aName; } int main() { ClassB B("Hallo World!"); return 0; }Fehlermeldungen:
c:\a\test\initbyctor\initbyctor.cpp(26) : error C2084: function 'ClassB::ClassB(std::string)' already has a body c:\a\test\initbyctor\initbyctor.cpp(14) : see previous definition of '{ctor}' c:\a\test\initbyctor\initbyctor.cpp(26) : error C2512: 'ClassA' : no appropriate default constructor available c:\a\test\initbyctor\initbyctor.cpp(32) : error C2264: 'ClassB::ClassB' : error in function definition or declaration; function not calledIch vermute das das Klammernpar {} in Zeile 14 als body für ctor ClassB gewertet wird. Ich bekomme aber kein Syntax ohne dieses Klammerpaar hin. Die anderen Fehler könnten Folgefehler sein.
Allgemein gefragt: Wo schaut ihr nach, wenn ihr ein C++ Syntaxproblem habt? Die Lehrbücher scheinen mir relativ ungeeignet, weil die ja nicht jeden möglichen Fall beschreiben können. Meist bleiben die bei den Standardfällen. Wäre da nicht ein BNF-Graph, ergänzt mit Erläuterungen, nötig? Ich habe bisher nichts brauchbares in der Richtung gefunden. Die Hilfe zu VC++ 2005 empfinde ich als sehr unzureichend. MS schafft es ja noch nicht einmal die Hilfe zu den einzelnen Sprachen sauber zu trennen. Wenn ich dann was von Visual Basic und .NET zu lesen bekomme, obwohl ich den Filter auf C++ gesetzt habe, dann kommt bei mir richtig Frust auf.
LG
Bernd
-
#include <string> class ClassA{ //... public: ClassB(std::string aName); //... ClassB::ClassB(std::string aName) : A(aName){ m_NameB = aName; }So?
EDIT: Desweiteren kannst du den Konstruktoren konstante Referenzen übergeben (const std::string&), was die Performanz etwas verbessert und "besserer Stil" ist. Und m_NameB kann ebenso in die Initialisierungsliste, auch wenn ich die nicht mag.

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