Vererbung und Konstruktoren
-
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