Vererbung und Konstruktoren
-
Hallo ihr lieben,
ich habe mal wieder ein paar Verständnisfragen

ERSTENS:
Die Fehlerausgabe:
http://codepad.org/BPgAGmqocc1plus: warnings being treated as errors
t.cpp: In constructor 'viereck::viereck(int, int, int, int)':
Line 26: warning: 'viereck::a2' will be initialized after
Line 20: warning: base 'punkt'
Line 20: warning: when initialized here
t.cpp: In copy constructor 'viereck::viereck(const viereck&)':
Line 21: warning: base class 'class punkt' should be explicitly initialized in the copy constructorWarum wird a2 später initialisiert, aber a1 macht keine Probleme?
Ich hab an das Bindungsproblem gedacht. Aber ich kann einen Konstruktor doch nicht virtual machen, eine andere Funktion hab ich doch nicht...ZWEITENS:
Belehrt wurde ich hier von der Dreierregel.
Während es in der Klasse punkt kein Problem ist, wie komme ich im Kopierkonstruktor von viereck an den Konstruktor von punkt?DRITTENS:
Der Destruktor von viereck. Ich bin immer skeptisch, wenn ich den ohne Argumente aufrufe. Aber hier rufe ich ja auch den Konstruktor einer anderen Klasse auf, mein Gefühl sagt mir, dass ich da noch was tun muss ... ?#include <iostream> #include <vector> class punkt{ public: punkt(int px=0, int py=0): x(px),y(py) {++zaehl; stelle=zaehl;} punkt(const punkt& other): x(other.x),y(other.y) {++zaehl;} ~punkt() {--zaehl;} void verschiebe(int dx, int dy); void ausgabe(); private: int stelle; static int zaehl; int x,y; }; class viereck : public punkt { public: viereck(int pa=1, int pb=1, int px=0, int py=0) : a1(pa), a2(pb), punkt(px,py) {} viereck(const viereck &other):a1(other.a1),a2(other.a2) {} // wie kommt der Punkt Konstruktor hier rein? ~viereck() {} // muss da irgendwas mit delete rein? Kann das leer bleiben? void ausgabe(); private: int a1; int a2; }; int main(int count, char **args) { std::vector<punkt> test; test.push_back(punkt(2,3)); test.push_back(punkt(1,1)); test.push_back(punkt(2,6)); test[0].ausgabe(); test.clear(); test.push_back(punkt(5,5)); test[0].ausgabe(); std::vector<viereck> test2; test2.push_back(viereck(4,6,8,8)); test2[0].ausgabe(); return 0; } void viereck::ausgabe() { std::cout << a1 <<'x' << a2 << ' ' ; punkt::ausgabe(); } int punkt::zaehl=0; void punkt::verschiebe(int dx, int dy) { x=dx; y=dy; } void punkt::ausgabe() { std::cout << zaehl << " [" << stelle << "] Punkt " << x << '/' << y << '\n'; }Ich bedanke mich wie immer für die tolle Hilfe!

-
Oh, je. Das Wurstbrot ist wieder der Supermarkt.
class viereck : public punkt? Das bedeutet so viel wie "ein viereck ist ein punkt". Ist das wohl wirklich eine gute Idee? Wo hast du Vererbung gelernt?Zu deinen sonstigen Fragen:
-Die Initialisierungsreihenfolge ist völlig unabhängig von der Reihenfolge in der Initialisierungsliste. Sie ist eindeutig festgelegt durch:
-Zuerst alle Basisklassen (ich meine aus dem Kopf heraus von rechts nach links, aber wenn du wirklich Mehrfachvererbung nutzt, dann guckst du lieber nochmal nach)
-Dann die Member, in der Reihenfolge, wie sie in der Klassendefiniton stehen
-Diese Regeln werden rekursiv angewandtBeim Destruktor geht es genau umgekehrt.
-Der Punktkonstruktor im Kopierkonstruktor kannst du so aufrufen:
viereck(const viereck &other):punkt(other),a1(other.a1),a2(other.a2) {}
Das geht, weil bei dir ein viereck auch ein punkt ist und other ist daher auch ein punkt. Ich hoffe du merkst, was das für ein unsinniges Design ist.// muss da irgendwas mit delete rein? Kann das leer bleiben?
Ich glaube, du hast den Sinn von Destruktoren und Konstruktoren noch nicht verstanden. Und auch nicht den der Dreierregel. Du brauchst hier weder Kopierkonstruktor noch Destruktor. Du hast hier bloß die "special member functions" nachgebaut, die auch automatisch erzeugt worden wären.
Destruktoren ruft man immer ohne Argumente auf. Nein, besser noch sollte ich sagen: Destruktoren ruft man nie auf.
~(außer in ganz wenigen Spezialfällen, die dir nie begegnen werden)~Womit lernst du C++?
P.S.: Ich habe doch neulich schon den Thread von Herrn Bebel heraus gesucht. War das nicht für dich?
-
nein nein nein ... *lach* kein Jürgen Wolf Buch!
Ich mache das erst seit einer Woche und davon hab ich das Wochenende gerade pausiert.
Ich dachte mir: ok ein Viereck hat eine Koordinate links unten und dann brauch ich nur noch die Seitenlänge. Dann brauch ich für einen Kreis nur einen Mittelpunkt und einen Radius, usw. Immerhin haben alle geometischen Objekte sowas gemeinsam.
Aber das Auto/Motor Beispiel war gut!!
(Tatsächlich komme ich von C)Ich teste grad einfach so rum, zum Verständnis.

Ich hab das Stroustrup Buch zum Nachschlagen. Außerdem das "Thinking in C++".
Aber ich bin grad ein bisschen von Kapitel zu Kapitel gesprungen. Wenn mir eine Frage in den Kopf kommt, dann probiere ich das erstmal aus! Vieles passiert intuitiv und dann kommt auch schonmal murks raus!
Die anderen Anmerkungen werde ich jetzt nachlesen.
DANKE!PS: Bebel? Nein, nicht dass ich wüsste.
-
Lymogry schrieb:
Ich dachte mir: ok ein Viereck hat eine Koordinate links unten und dann brauch ich nur noch die Seitenlänge. Dann brauch ich für einen Kreis nur einen Mittelpunkt und einen Radius, usw. Immerhin haben alle geometischen Objekte sowas gemeinsam.
Über dieses Problem sind schon vorher einige Leute gestolpert. Es gibt sogar einen Wikipedia-Eintrag dazu: Kreis-Ellipse-Problem.
-
Lymogry schrieb:
Ich dachte mir: ok ein Viereck hat eine Koordinate links unten und dann brauch ich nur noch die Seitenlänge...
Grundsätzlich habe ich mal ein Wort hervorgehoben. Ich kenne sehr viele die Vererbung immer zum sparen von Code verwenden, am Schluss damit aber sehr unflexible Strukturen haben (Einen solchen Fall haben wir auch an mehreren Stellen in unseren Projekt, aber die Erkenntnis ist beim Chef noch nicht so ganz angekommen, obwohl jeder andere Entwickler mit dem Konstrukt massive Probleme hat - u.a. wenn wir etwas in diese Hierarchie einpflegen müssen).
Man sollte bei Vererbung nach Möglichkeit das Liskovsches Substitutionsprinzip berücksichtigen. Stark zusammengefasst: Nutze Vererbung nur, wenn du auch sagen kannst "IST EIN". Es gibt Ausnahmen, aber die sollte man sich gut überlegen und nicht nur wegen Schreibfaulheit nutzen.
Grundsätzlich sollte man starke Bindung nur mit Bedacht verwenden; Vererbung gilt in vielen Sprachen als die stärkste Form der Bindung (in C++ ist friend noch stärker). "HAT" deutet immer auf Komposition.
-
-Zuerst alle Basisklassen
Ja, von Rechts nach Links:
Bases and members are destroyed in the reverse order of the completion of their constructor
Und hier die Reihenfolge des Konstruktors (die gleichzeitig auch die umgekehrte Reihenfolge des Destruktors ist):
§12.6.2/10 schrieb:
In a [..] constructor, initialization proceeds in the following order:
— First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
— Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
— Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
— Finally, the compound-statement of the constructor body is executed.Das Wurstbrot ist wieder der Supermarkt.
Vice versa - Supermarkt leitet von Wurstbrot ab.Aufpassen in welches Wurstbrot du beißt - es könnte aus Beton bestehen.
Edit: Nein -
- es war schon richtig, Wurstbrot von Supermarkt. Wie Bashar schon richtig formulierte:Bashar schrieb:
Wurst erbt dann auch noch virtual vom Supermarkt, dann macht er daraus ein deadly WurstBrot of death
Nun, egal wie herum, ist beides Unsinn.
-
Da wollte ich mir ein einfaches Beispiel zum Verständnis konstruieren und lerne dabei wie es nicht geht!

Ihr seid spitze! Vielen Dank!
Übrigens, wenn ich die Vererbung wegmache, sieht es so aus:
#include <iostream> #include <vector> class viereck { public: viereck(int pa=1, int pb=1, int px=0, int py=0) : a1(pa), a2(pb), x(px), y(py) {++zaehl; stelle=zaehl;} viereck(const viereck &other): a1(other.a1),a2(other.a2), x(other.x), y(other.y) {++zaehl;} ~viereck() {--zaehl;} void verschiebe(int dx, int dy); void ausgabe(); private: int a1; int a2; int stelle; static int zaehl; // Anzahl der Elemente int x,y; // Koordinaten }; class kreis { public: kreis(int pr=1, int px=0, int py=0) : r(pr), x(px), y(py) {++zaehl; stelle=zaehl;} kreis(const kreis &other): r(other.r), x(other.x), y(other.y) {++zaehl;} ~kreis() {--zaehl;} void verschiebe(int dx, int dy); void ausgabe(); private: int r; // Radius int stelle; static int zaehl; // Anzahl der Elemente int x,y; // Koordinaten }; int main(int count, char **args) { std::vector<viereck> test2; test2.push_back(viereck(4,6,8,8)); test2[0].ausgabe(); std::vector<kreis> test; test.push_back(kreis(5,7,9)); test[0].ausgabe(); return 0; } void viereck::ausgabe() { std::cout << "Viereck: " << a1 << 'x' << a2 << " #" << zaehl << " [" << stelle << "] Koordinate (" << x << '/' << y << ") \n"; } void kreis::ausgabe() { std::cout << "Kreis: " << r << " #" << zaehl << " [" << stelle << "] Koordinate (" << x << '/' << y << ") \n"; } int viereck::zaehl=0; int kreis::zaehl=0; void viereck::verschiebe(int dx, int dy) { x=dx; y=dy; } void kreis::verschiebe(int dx, int dy) { x=dx; y=dy; }Beide Klassen benutzen x und y Koordinaten und eine Funktion verschiebe() die das selbe tut. Ich hätte gedacht, sowas kann man elegant zusammen fassen.
(ok, ich bin wohl noch nicht soweit. Aber interessant ist es schon!
)PS: Für Vererbung werde ich nochmal genauer in den Büchern lesen. Komme grad aus der Bibliothek und habe deren Bestand mal auseinander genommen und noch C++ Primer, Der C++ Programmierer und Effektiv C++ Programmieren (Scott Meyers) mitgenommen.
Literatur kann man nie genug haben! ^^
-
Lymogry schrieb:
Beide Klassen benutzen x und y Koordinaten und eine Funktion verschiebe() die das selbe tut. Ich hätte gedacht, sowas kann man elegant zusammen fassen.
Richtig. Dieses gemeinsame Subset an Funktionalität ergibt aber kein vollständiges geom. Objekt, es ist eine Abstraktion. Nicht zufällig, sind Basisklassen in solche Hierarchien daher normalerweise abstrakt, der Versuch von einer konkreten Klasse abzuleiten (also einer Klasse, von der Objekte existieren, die nicht Basisklassensubobjekte eines anderen Objektes sind), ist praktisch immer ein Fehler.
Das könnte dann zum Beispiel so aussehen:#include <iostream> #include <vector> class shape { public: shape(int x, int y, int* zaehler) : x(x), y(y), stelle(++*zaehler), zaehler(zaehler) {} shape(const shape& other) : x(other.x), y(other.y), stelle(++*other.zaehler), zaehler(other.zaehler) {} // C++11 // shape(const shape& other) : shape(other.x, other.y, other.zaehler) {} protected: shape& operator=(const shape& other) { x = other.x; y = other.y; return *this; } public: virtual ~shape() { --*zaehler; }; virtual void verschiebe(int dx, int dy); virtual void ausgabe() = 0; protected: int x; int y; int stelle; int* zaehler; }; void shape::verschiebe(int dx, int dy) { x = dx; y = dy; }; class viereck : public shape { public: viereck(int pa=1, int pb=1, int px=0, int py=0) : shape(px, py, &zaehl), a1(pa), a2(pb) {} void ausgabe(); private: int a1; int a2; static int zaehl; // Anzahl der Elemente }; class kreis : public shape { public: kreis(int pr=1, int px=0, int py=0) : shape(px, py, &zaehl), r(pr) {} void ausgabe(); private: int r; // Radius static int zaehl; // Anzahl der Elemente }; int main(int count, char **args) { std::vector<viereck> test2; test2.push_back(viereck(4,6,8,8)); test2[0].ausgabe(); std::vector<kreis> test; test.push_back(kreis(5,7,9)); test[0].ausgabe(); return 0; } void viereck::ausgabe() { std::cout << "Viereck: " << a1 << 'x' << a2 << " #" << zaehl << " [" << stelle << "] Punkt " << x << '/' << y << '\n'; } void kreis::ausgabe() { std::cout << "Kreis: " << r << " #" << zaehl << " [" << stelle << "] Punkt " << x << '/' << y << '\n'; } int viereck::zaehl=0; int kreis::zaehl=0;
-
es wurde nun bestimmt schon 10 mal in 10 threads gesagt: rule of three
ohne bringt dein instance-counter nicht viel...
-
Lymogry schrieb:
Übrigens, wenn ich die Vererbung wegmache, sieht es so aus:
class viereck { ... }; class kreis { ... };Wo ist hier (d)eine Vererbung?
-
wow, camper. Vielen Dank!
Ich versuche zu entschlüsseln:
protected: shape& operator=(const shape& other) { x = other.x; y = other.y; return *this; }Das ist der Zuweisungsoperator nehme ich an? Im anderen Thread bin ich schon darüber gestolpert, aber das damalige Beispiel brauchte keinen.
Jetzt braucht es doch einen?Nicht zufällig, sind Basisklassen in solche Hierarchien daher normalerweise abstrakt,
Merke: versuche keine sprachlogischen Probleme bei Vererbung zu erzeugen. Fürs Verständnis des Quellcodes.
Hat aber noch mehr Gründe nehme ich an ... ?
Eigentlich ist diese Shape Klasse ja genau das, was ich mit meiner Punkt Klasse wollte, es aber nicht hinbekommen habe
Sie heißt anders und schon gibts kein "Ist-Ein" Problem.virtual ~shape() { --*zaehler; };warum ist der Destruktor virtual?

virtual void ausgabe() = 0;Und das ist zum Reservieren da? Weil es nachher überladen wird, oder?
@text2121
text2121 schrieb:
es wurde nun bestimmt schon 10 mal in 10 threads gesagt: rule of three
ohne bringt dein instance-counter nicht viel...auf wen beziehst du dich? Hier ist doch die Dreierregel überall vorhanden?
Wutz schrieb:
Wo ist hier (d)eine Vererbung?
Den Kommentar verstehe ich jetzt nicht?!
Mir wurde da angeraten, keine Vererbung zu benutzen. Also hab ich das auseinander genommen und einmal ohne gepostet.
Und Camper hat eine Vererbungsoption gegeben.
-
warum ist der Destruktor virtual?
Weil sonst nicht der Destruktor der abgeleiteten Klasse, sondern nur der der Basisklasse aufgerufen werden würde, wenn ich einen Basisklassenzeiger an delete übergebe. Ist ohne Beispiel schwer zu erklären.
struct Base { ~Base(){ std::cout << "Falsch!"; } }; struct Derived : Base { ~Derived(){ std::cout << "Nicht "; } }; Base* b = new Derived; delete b;(Ungetestet)
Und das ist zum Reservieren da? Weil es nachher überladen wird, oder?
So ist es. Diese Funktion ist pure virtual und macht die Klasse abstrakt, die sie deklariert.
Jetzt braucht es doch einen?
Offensichtlich.
P.S.: Ich bins.
-
- schrieb:
Weil sonst nicht der Destruktor der abgeleiteten Klasse, sondern nur der der Basisklasse aufgerufen werden würde, wenn ich einen Basisklassenzeiger an delete übergebe.
Dann hat mich ja mein Gefühl nicht getrübt, ich hab doch anfangs das hier geschrieben "Aber hier rufe ich ja auch den Konstruktor einer anderen Klasse auf, mein Gefühl sagt mir, dass ich da noch was tun muss ... ? "

Hab mir schon gedacht, dass ich den der Oberklasse auch irgendwie aufrufen muss.Offensichtlich.
aha!
trivial!
(Ironie
)- schrieb:
P.S.: Ich bins.
Ja!
lol