Vererbung abstrakter Klassen
-
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...