Vererbung abstrakter Klassen
-
Hallo,
ich übe gerade die Vererbung von abstrakten Klassen, nur leider läuft es nicht wie gewünscht.
Die Basisklasse besitzt eine rein virtuelle Methode (reduzieren), welche als Parameter einen Zeiger von einem Basis-Objekt entgegennimmt.Diese wird von der Klasse Abgeleitet geerbt und mit einem Anweisungsrumpf versehen.
In der Main-Methode sind zwei basis Zeiger, welche zwei abgeleitet-Objekte auf dem Heap ablegen, deren Konstruktor einen int Wert entgegennimmt, welcher die anzahl auf 100 bzw. 10 setzt.
Nun rufe ich die Methode "reduzieren" auf, welche die Anzahl des zweiten Objekts 1 setzt.Allerdings wird bei der Ausgabe vor dem Aufrufen der Methode die Anzahl des Objekts falsch angezeigt.
Ausgabe vor Methodenaufruf:
-842150451
statt 10, wie ich es dem Konstruktor übergeben hatte.Nach dem Methodenaufruf stimmt dann aber die Anzahl:
1Für die Basis-Klasse hatte ich keinen Konstrukor geschrieben, wodurch es also nur einen Standardkonstruktor für die Basis-Klasse gibt...
Wenn ich allerdings den Methodenrumpf der Reduzieren-Methode so umschreibe:
void abgeleitet::reduzieren(basis *basis) { abgeleitet *abge = dynamic_cast<abgeleitet*> (basis); abge->anzahl = 1; }
und in der main-Methode statt einem Zeiger auf die basis- einen Zeiger auf abgeleitet-Klasse nehme, dann funktioniert es natürlich.
Allerdings ist es ja dann komplett sinnlos überhaupt zu vererben, da man dann natürlich eine methode schreiben könnte, die als Parameter einfach Zeiger von abgeleitet-Objekten entgegennimmt.Außerdem kann ich "int anzahl" der Basis-Klasse nicht in der abgeleiteten Klasse verwenden.
Ich wollte in den Konstruktor der abgeleiteten Klasse das hineinschreiben:abgeleitet::abgeleitet(int _anzahl) : anzahl(_anzahl) { }
Dann kommt allerdings: "error C2614: 'abgeleitet': Unzulässige Elementinitialisierung: 'anzahl' ist weder Basis noch Element".
Somit muss ich int anzahl nochmals in der abgeleiteten Klasse deklarieren, um den Konstruktor nutzen zu können?Hier mal alle Klassen:
basis.h:
#ifndef __BASIS_INCLUDED__ #define __BASIS_INCLUDED__ class basis { public: int anzahl; virtual void reduzieren(basis *basis) = 0; }; #endif //__BASIS_INCLUDED__
abgeleitet.h:
#ifndef __ABGELEITET_INCLUDED__ #define __ABGELEITET_INCLUDED__ #include "basis.h" class abgeleitet : public basis { public: int anzahl; abgeleitet(int _anzahl); virtual void reduzieren(basis *basis); }; #endif //__ABGELEITET_INCLUDED__
abgeleitet.cpp:
#include "abgeleitet.h" abgeleitet::abgeleitet(int _anzahl) : anzahl(_anzahl) { } void abgeleitet::reduzieren(basis *basis) { basis->anzahl = 1; }
main.cpp:
#include<iostream> #include "abgeleitet.h" using namespace std; int main() { abgeleitet *abgeleitet_ptr; abgeleitet_ptr = new abgeleitet(100); abgeleitet *abgeleitet2_ptr; abgeleitet2_ptr = new abgeleitet(10); cout << abgeleitet2_ptr->anzahl << endl; abgeleitet_ptr->reduzieren(abgeleitet2_ptr); cout << abgeleitet2_ptr->anzahl << endl; basis *abgeleitet3_ptr; abgeleitet3_ptr = new abgeleitet(100); basis *abgeleitet4_ptr; abgeleitet4_ptr = new abgeleitet(10); cout << abgeleitet4_ptr->anzahl << endl; abgeleitet3_ptr->reduzieren(abgeleitet4_ptr); cout << abgeleitet4_ptr->anzahl << endl; }
Ich gehe davon aus, dass die Zahl (-842150451) was mit dem Konstruktor zu tun hat. Evtl. wird in der Basis-Klasse nach einem passenden Konstruktot gesucht, obwohl es dort keinen gibt.
Ich hoffe irgendjemand hat verstanden was ich versucht hab zu beschrieben und kann mir irgendwie weiterhelfen.
-
Der Member anzahl in der Klasse basis ist nicht initialisiert.
1. der Klasse basis einen Konstruktor verpassen.
2. diesen Konstruktor in der abgeleiteten Klasse aufrufen.
3. lass den Member anzahl in der abgeleiteten Klasse weg. Er ist ja doppelt.Bsp:
class Base { public: Base(int value) : _value(value) {} virtual ~Base() {} private: int _value; }; class Derived : public Base { public: Derived(int theValue) : Base(theValue) {} };
Zur Methode reduzieren äussere ich mich im Moment nicht (weil keine Zeit, ist mir aber nicht entgangen). Es scheint aber Du hast da noch was falsch verstanden.
Simon
Edit:
- Punkt 3 hinzugefügt
-
Mir scheint ebenfalls, dass du vieles nicht so ganz begriffen hast.
1. Abstrakte Basisklassen haben üblicherweise keine Datenmember. Auch wenn es möglich ist, sei dir gesagt, dass es Unsinn ist.
2. Wenn du Objektorientiert programmierst, dann änderst du üblicherweise das aktuelle Objekt und du übergibst nicht einen Zeiger auf ein anderes und änderst dann das. Das ist ebenfalls Unsinn.
3. Du scheinst den Sinn von virtuellen Funktionen auch nicht ganz begriffen zu haben.
Dazu kommt noch, dass ich deiner Beobachtung nicht ganz glaube. Wie gesagt gibst du zuerst die
anzahl
der abgeleiteten Klasse aus, weil das ja der statische Typ ist, wird diese Member genommen. Dann änderst du über einen Zeiger auf die Basis dieanzahl
der Basisklasse. Du änderst dieanzahl
der abgeleiteten Klasse gar nicht. Dann kann die nachher unmöglich eine andere Zahl sein. Ich habe mal ein kleines Minimalbeispiel von deinem Code gemacht und das mir meiner Vermutung bestätigt:struct base { base ( int n ) : a_ ( n ) {} virtual void foo () = 0; int a_; }; struct A : base { A ( int n ) : base ( n ), a_ ( 0 ) {} virtual void foo () {} int a_; }; int main () { A* pa = new A ( 2 ); std::cout << pa->a_ << " / " << ( (base*)pa )->a_ << std::endl; // 0 / 2 ( (base*)pa)->a_ = 200; std::cout << pa->a_ << " / " << ( (base*)pa )->a_ << std::endl; // 0 / 200 }
Falls du nicht die gleichen Ausgaben bekommst, dann wird wohl dein Compiler da was falsch machen.
Der Sinn von virtuellen Funktionen ist der, dass du über einen Basisklassen Zeiger ohne den genauen Typ zu wissen Funktionen darauf aufrufen kannst.
struct base { virtual void foo () = 0; virtual ~base () {} // Destruktoren immer virtual deklarieren, sobald du eine andere virtuelle Funktion hast! }; struct A : base { void foo () { std::cout << "ich bin ein A\n"; } }; struct B : base { void foo () { std::cout << "ich bin ein B\n"; } }; void out ( base* b ) { b->foo (); // egal was b jetzt genau für einen Typ hat } int main () { A a; B b; out ( &a ); out ( &b ); }
Wie du hier siehst, kannst du eine Funktion aufrufen, ohne den genauen Typ zu kennen. Alles, was du weiss ist, dass das Objekt die Schnittstelle der Basisklasse anbietet und die kannst du dann auch benutzen. Zusätzlich könnte A und B jedoch noch mehr Funktionen (je unterschiedlich haben), welche du dann aber nicht benutzen kannst.
-
drakon schrieb:
1. Abstrakte Basisklassen haben üblicherweise keine Datenmember. Auch wenn es möglich ist, sei dir gesagt, dass es Unsinn ist.
So hart würde ich das nicht formulieren. Es kann durchaus Fälle geben, in denen eine Basisklasse schon Eigenschaften trägt, die jede abgeleitete Klasse haben wird, aber trotzdem noch zu wenig spezifisch für eine konkrete Klasse ist.
-
Ich unterscheide zwischen Basis Klasse und Interface.
Im ersten Fall gehts dann um implementation Vererbung und im zweiten Falls gehts um interface Vererbung.Simon
-
Nexus schrieb:
drakon schrieb:
1. Abstrakte Basisklassen haben üblicherweise keine Datenmember. Auch wenn es möglich ist, sei dir gesagt, dass es Unsinn ist.
So hart würde ich das nicht formulieren. Es kann durchaus Fälle geben, in denen eine Basisklasse schon Eigenschaften trägt, die jede abgeleitete Klasse haben wird, aber trotzdem noch zu wenig spezifisch für eine konkrete Klasse ist.
Ja, eine normale Basisklasse schon, aber ich sehe keinen Grund, warum eine abstrakte Basisklasse Datenmember haben sollte.
-
drakon schrieb:
Ja, eine normale Basisklasse schon, aber ich sehe keinen Grund, warum eine abstrakte Basisklasse Datenmember haben sollte.
Eben weil die Basisklasse noch zu wenig konkrete Informationen trägt, um bereits Objekte erstellen zu können. Um zwei Beispiele von mir zu nehmen:
In einem Jump'n'Run habe ich eine Basisklasse
Collidable
, welche für kollidierbare Spielelemente steht (Gegner etc. erben davon). Alle kollidierbaren Objekte besitzen zum Beispiel eine Position und eine Geschwindigkeit, also habe ich zwei Vektoren als Member. Trotzdem muss die Basisklasse abstrakt sein, weil man nicht einfach ein Objekt erstellen kann, das mit der Welt zwar kollidiert, aber von dem man sonst nichts weiss. Zum Beispiel ist eine MethodeThink()
, die darüber bestimmt, welche Spiellogik ein Element jedes Frame ausführen soll, rein virtuell. Je nachdem, ob es sich um einen Gegner, um den Spieler oder was auch immer handelt, sieht die Implementierung anders aus.Ähnlich ist es bei einem kleinen GUI-Tool: Die Basisklasse
Widget
speichert zum Beispiel einebool
-Variable, die sagt, ob eine GUI-Komponente gerade aktiv ist (z.B. ob man eine Schaltfläche drücken kann, in ein Textfeld schreiben kann, etc.). Die Klasse ist aber abstrakt, weil man nicht einfach eine GUI-Komponente ohne weitere Spezifizierung erstellen kann.Kommt also ab und zu vor. Wir wollen ja nicht bei den Interfaces von Java landen.
-
Erstmal vielen Dank für die schnellen Antworten.
Nachdem ich wie von theta vorgeschlagen der Basis einen Konstruktor gegeben habe, hat alles so funktioniert wie ich es mir vorgestellt hatte.
Und ja, ich habe wohl wirklich so einiges noch nicht verstanden.
Ich werde nochmal alles löschen und es so machen, wie drakon es erklärt hat, in der Hoffnung das alles zu verinnerlichen und mal richtig durchzublicken.
-
Naja. Für sowas mache ich halt eine Zwischenklasse, welche dann auch einige der Funktionen bereits implementiert. Finde ich trennt die Schnittstelle besser von jedweder Implementierung.
Also z.B bei einem GUI Element. Wenn du da jetzt z.B einige Informationen drin hast, welche von allen Elementen gebraucht werden und dann irgendwann auf eine riesen, fantastische Idee kommst, wie man ein GUI Element doch auch noch anderst machen könnte, dann bist du auf eine Art an die Implementierung, die du schon hattest gebunden. (Funktionen kannst du natürlich überscheiben, aber bei Datenmemern ist das dann aber unschön, wenn die einfach so drin rumschwirren).
Wenn du jetzt aber eine Defaul Implementierung von der Schnittstelle ableitest und dann die Elemente davon ableitest, dann kannst du nachher immernoch eine völlig andere Kreation von GUI Elementen mit der gleichen Schnittstelle einfügen. (macht bei GUI vielleicht nicht so viel Sinn, aber es gibt sicher Fälle, wo das Sinn machen kann).
-
In meinen Beispielen sähe ich allerdings nicht wirklich einen Vorteil darin, sondern es würde nur die Komplexität erhöhen, weil die Klassenhierarchie tiefer wird. Besonders bei der GUI werde ich wahrscheinlich auch so schon genügend oft vererben. Ich meine, z.B.
Widget
hält seine Memberprivate
, nichtprotected
. Zugegriffen wird in abgeleiteten Klassen auch über Funktionen. Von daher könnte ich auch die Implementierung ändern...Allerdings kann ich mir bei deiner Sache gewisse Dinge immer noch nicht erklären. Wie würdest du das beim
Collidable
-Beispiel machen? Du hast das Problem, dass erstens gewisse Operationen (z.B.Think()
) erst in konkreten Klassen wiePlayer
oderEnemy
bekannt sind. Folglich muss die Klasse abstrakt sein, auch eine eventuelle Zwischenklasse – es sei denn, du weichst auf fragwürdige Dinge wie Dummy-Implementierungen aus. Zweitens möchtest du gemeinsame Dinge wie Position und Geschwindigkeit bereits implementiert haben, um für deren Abfrage keine virtuelle Funktionen zu benötigen und um Codeduplizierung zu vermeiden. Wie handhabst du sowas?
-
Nexus schrieb:
Allerdings kann ich mir bei deiner Sache gewisse Dinge immer noch nicht erklären. Wie würdest du das beim
Collidable
-Beispiel machen? Du hast das Problem, dass erstens gewisse Operationen (z.B.Think()
) erst in konkreten Klassen wiePlayer
oderEnemy
bekannt sind. Folglich muss die Klasse abstrakt sein, auch eine eventuelle Zwischenklasse – es sei denn, du weichst auf fragwürdige Dinge wie Dummy-Implementierungen aus. Zweitens möchtest du gemeinsame Dinge wie Position und Geschwindigkeit bereits implementiert haben, um für deren Abfrage keine virtuelle Funktionen zu benötigen und um Codeduplizierung zu vermeiden. Wie handhabst du sowas?Ok, ich war vielleicht mit dem abstrakt ein wenig hart. Ich meinte eher, wenn man eine abstrakte Klasse als Schnittstelle hat. Die Zwischenklasse kann oder kann nicht abstrakt sein, je nachdem, was man für Funktionen hat.
Um mein Beispiel ein wenig zu verdeutlichen. Sagen wir du hast eine GUI Klasse, welche die Basis für alle anderen Elemente sein soll. Jetzt kannst du dir aber denken, dass die Meisten GUI Elemente ja von einem Typ
Window
sind. Also implementierst du erstmalWindow
auf dieser Schnittstelle. (Das Beispiel macht sogar sehr viel Sinn, da z.B unter Windows praktisch alles ein Window ist. ;)). Nun kannst duButton
,Textfield
usw. von Window erben lassen, oder halt auch eine Window Instanz so erstellen, wo du üblicherweise Elemente anfüngen kannst. Nun kann es aber auch Elemente geben, welche so überhaupt nicht ins Konzept von einem Window passen. aber trotzdem über dieselbe Schnittstelle angesprochen werden sollen (mir fällt gerade kein gutes Beispiel ein, aber ich denke der Punkt ist klar) und da kannst du dann völlig unabhängig von einer üblichen Implementierung dein Element definieren.Die Schnittstelle sollte imo also völlig unangetastet sein von jeglicher Implementierung. Aber du hast recht, abstrakt kann auch die abgeleitete Klasse werden, je nach dem. (Aber ich habe ja geschrieben: üblicherweise.
- Und bei abstrakten Klassen, die als Schnittstelle fungieren sollte das imo wirklich nicht gemacht werden).
-
Okay ja. Ich muss halt schauen, ob sich eine zusätzliche Zwischenklasse lohnt, momentan sind die einzelnen GUI-Komponenten recht verschieden (aber ich habe auch erst sehr wenige ;)). Ich habe mir auch überlegt, z.B. vielleicht später etwas wie
Editable
als Basisklasse vonTextField
,TextArea
und solchen Sachen zu machen, aber das wird sich noch zeigen. Wenn es geht, will ich nicht zu viele solcher Klassen haben. Mal schauen...