Abstrakte Klassen, Vererbung, Referenzen und Parameterügergabe
-
Hallo mal wieder,
obwohl das Thema ein bisschen ähnlich wie mein letztes klingt, gehört es doch in einen eigenen Thread.
Mein Problem ist folgendes:
Ich habe eine abstrakte Basisklasse, von der ich eine andere Klasse abgeleitet habe.
Nun habe ich eine Methode, die Referenzen vom Typ der abstrakten Basisklasse übergeben bekommt, damit ich diese Methode auch für alle abgeleitete Klassen verwenden kann (bin ich von Java gewohnt).
Nur klappt das leider nicht!Wenn ich dieser Methode versuche eine Referenz der abgeleiteten Klasse zu übergeben, meckert mich der Compiler an, dass er einen Typ der abstrakten Basisklasse erwartet.
Ja warum das denn? Muss ich hier casten?Denke mal, dass das bestimmt ein dummer Anfängerfehler ist (bin ja auch Anfänger).
Kann das bitte jemand aufklären?!
Danke schon mal
Ciao
-
skizzier das ganze doch bitte einmal mit code. damit lässt es sich im allgemeinen leichter diskutieren.
-
Ich habe mal das, was du mit Worten beschreibst als Code aufgeschrieben, der sich kompilieren läßt. Vielleicht findest du ja daran schon deinen Fehler, sonst hilft nur der konkrete Code...
class AbstraktBasis { public: virtual int TestMethode(void) = 0; }; class KonkretAbgeleitet : public AbstraktBasis { public: virtual int TestMethode(void); }; bool TestFunktion(AbstraktBasis &par) { par.TestMethode(); return true; } int main(void) { KonkretAbgeleitet foo; TestFunktion(foo); }
Felix
-
Also hier mal meine Sourcen.
Normal trenne ich zw. Header und Implementierung, hier ist z.T. alles im Header.Wie ist das denn in C++, wenn man eine Klasse im selben File deklariert und implementiert und diese Klasse woanders verwenden will? Da kann man dann doch kein #include mehr machen, oder?
Hier mal meine Basisklasse:
#ifndef APPLICATIONC_H #define APPLICATIONC_H class ApplicationC { public: ApplicationC() {} virtual ~ApplicationC() = 0; virtual void destroy() = 0; private: }; #endif ApplicationC::~ApplicationC() { destroy(); }
Hier meine konkrete Klasse:
#ifndef TESTAPP_H #define TESTAPP_H #include <iostream> #include "ApplicationC.h" class TestApplicationC : public ApplicationC { public: TestApplicationC(); ~TestApplicationC(); void destroy(); }; #endif TestApplicationC::TestApplicationC() {} TestApplicationC::~TestApplicationC() { destroy(); } void TestApplicationC::destroy() { cout << "Destroy!" << endl; }
Hier die verwendende Klasse:
#ifndef QUITHANDLERC_H #define QUITHANDLERC_H #include "HandlerC.h" #include "ApplicationC.h" class QuitHandlerC : public HandlerC { public: QuitHandlerC(ApplicationC& application); ~QuitHandlerC(); void handle(); private: ApplicationC& appl; }; #endif QuitHandlerC::QuitHandlerC(ApplicationC& application) : appl(application) {} QuitHandlerC::~QuitHandlerC() {} void QuitHandlerC::handle() { appl.destroy(); }
Und hier die Verwendung im Programm:
... TestApplicationC testApp(); QuitHandlerC handler(testApp); ...
Der Compiler sacht nun, dass er keine passende Methode QuitHandlerC( TestApplicationC (&)()) findet???
Was hab ich denn alles falsch gemacht?
-
TestApplicationC testApp();
verglichen mit
int function();sieht wie n prototyp einer funktion aus, nicht?
TestApplicationC testApp;
ist was du suchst.
-
Vielen Dank! Das wars!
Gibts noch n Tipp hinsichtlich der Problematik mit Deklaration und Definition einer Klasse im selben File und Verwendung derselben Klasse in unterschiedlichen Dateien?
Bei den geposteten Sourcen von mir handelt es sich immer um .h Dateien, die z.T. mehrfach inkludiert werden.
Das führt im vorliegenden Fall zu ner Fehlermeldung vom Assembler, da er das Symbol für ApplicationC mehrfach findet.
Muss ich diese Klassen halt auch noch trennen in *.cc und *.h, so wie meine restlichen Klassen auch.
Ciao
-
Ja, Deklarationen immer in Header-Dateien (.h) und Implementationen in Source-Dateien (.cpp).
Nur wenn man z.B. private Klassen hat, kann man diese auch komplett in cpp-Dateien schreiben, aber niemals Implementationen in Header-Dateien schreiben.Aber es gibt zu jedem natürlich eine Ausnahme: Template-Klassen und -Methoden müssen in Header-Dateien geschrieben werden (bzw. in den Header-Dateien includiert werden - solange das Schlüsselwort "export" nicht unterstützt wird...).
-
Normalerweise kommt die Deklaration der Klasse in die Header-Datei und die Implementierung der Mehtoden kommt in eine zugehörige .cpp Datei.
Bei dir sieht das dann so aus:
ApplicationC.h:
#ifndef APPLICATIONC_H #define APPLICATIONC_H class ApplicationC { public: ApplicationC() {} virtual ~ApplicationC(); //Wieso war hier eigentlich das =0?? virtual void destroy() = 0; private: }; #endif
ApplicationC.cpp:
#include "ApplicationC.h" ApplicationC::~ApplicationC() { destroy(); }
TestApp.h:
#ifndef TESTAPP_H #define TESTAPP_H #include <iostream> #include "ApplicationC.h" class TestApplicationC : public ApplicationC { public: TestApplicationC(); ~TestApplicationC(); void destroy(); }; #endif
TestApp.cpp:
TestApplicationC::TestApplicationC() {} TestApplicationC::~TestApplicationC() { destroy(); } void TestApplicationC::destroy() { cout << "Destroy!" << endl; }
So in etwa...
Felix
EDIT: Dadurch gibt es dann nicht mehr die Linker-Fehler (der Assembler hat damit eigentlich wenig zu tun), wenn du einen Header mehrfach in verschiedenen Dateien hast, weil so keine mehrfachen Methodendefinitionen auftreten.
-
Das meinte ich ja damit, dass ich das bisher mit meinen anderen Klassen auch so gemacht habe (also Header und Implementierung in getrennte Dateien).
Aber wie ist das Vorgehen, wenn man beides in eine Datei steckt und diese dann in unterschiedlichen anderen Klassen verwenden will?
-
Achso, dann habe ich dich zuerst falsch verstanden.
Dafür gibt es drei "Möglichkeiten"
- Wieso sollte man das wollen, was du willst, wenn es anders doch auch, besser und ohne Mehraufwand geht?
Falls man es doch unbedingt machen will:
- Inline-Methoden benutzen
Beispiel:
Klasse1.h
#ifndef _KLASSE1_H #define _KLASSE1_H #include <iostream> class Klasse1 { public: inline virtual void TestMethode(); }; void Klasse1::TestMethode() { std::cout << "Klasse1"; } #endif
Klasse2.h
#ifndef _KLASSE2_H_ #define _KLASSE2_H_ #include "klasse1.h" #include <iostream> class Klasse2 : public Klasse1 { public: inline virtual void TestMethode(); private: }; void Klasse2::TestMethode() { std::cout << "Klasse2"; } #endif
main.cpp
#include "klasse1.h" #include "klasse2.h" int main(void) { Klasse2 testobjekt; testobjekt.TestMethode(); testobjekt.Klasse1::TestMethode(); return 0; }
Hierbei gibt es aber die Wechselwirkungen zwischen virtuellen und inline-Methoden, weil Methoden bei denen zur Laufzeit nicht feststeht, welche aufgerufen wird, nicht inline eingefügt werden können. Es läßt sich aber kompilieren...
- Anonyme Namespaces benutzen
Beispiel:
Klasse1.h#ifndef _KLASSE1_H #define _KLASSE1_H #include <iostream> class Klasse1 { public: virtual void TestMethode(); }; namespace { void Klasse1::TestMethode() { std::cout << "Klasse1"; } } #endif
Klasse2.h:
#ifndef _KLASSE2_H_ #define _KLASSE2_H_ #include "klasse1.h" #include <iostream> class Klasse2 : public Klasse1 { public: virtual void TestMethode(); private: }; namespace { void Klasse2::TestMethode() { std::cout << "Klasse2"; } } #endif
main.cpp:
#include "klasse1.h" #include "klasse2.h" int main(void) { Klasse2 testobjekt; testobjekt.TestMethode(); testobjekt.Klasse1::TestMethode(); return 0; }
Bei dieser Methode bin ich mir aber nicht sicher, ob das wirklich immer so toll funktioniert, die ist mir gerade nur eingefallen
Felix
-
Also ich bin jetzt aber doch der Meinung, dass die Klasse im gleichen Namespace wie ihre Methoden liegen muss - sonst klappt doch der Lookup schon garnicht mehr...
-
Phoemuex schrieb:
Achso, dann habe ich dich zuerst falsch verstanden.
Dafür gibt es drei "Möglichkeiten"
- Wieso sollte man das wollen, was du willst, wenn es anders doch auch, besser und ohne Mehraufwand geht?
Falls man es doch unbedingt machen will:
Danke.
Hm, war mir nicht sicher, ob das in C++ immer noch so gemacht wird mit Header- und Sorucedatei, oder ob das nur in C so sein sollte und in C++ andere Konventionen gelten. Hab kein größeres Problem damit immer 2 Files anzulegen.
-
Phoemuex schrieb:
- Anonyme Namespaces benutzen
dann muss auch die klasse im anonymen namespace sein - und dann handelt es sich nicht mehr um eine mehrfache definition derselben klasse (bzw. deren memberfunktionen) in mehreren ÜEs (die sind höchtens layout-komaptibel).
-
Ähm, also wegen dem nicht funktionieren:
Ich habe das mit dem Compiler, der bei Code::Blocks dabei ist genauso wie im Beispiel geschrieben und das Programm ließ
- sich kompilieren
- linken
- lief auch mit der erwarteten Ausgabe...
Kann aber halt sein, dass es trotzdem nicht Standard-konform ist...
-
also vc++8.0 hat schon mal was dagegen. und wenn ich ein bisschen stöbere finde ich bestimmt auch eine aussage im standard, die dieses verhalten unterstützt oder sogar verlangt. übrigens hast du es in diesem beispiel ja noch nicht mit mehreren ÜEs zu tun. funktioniert das linken dann immer noch ?
-
da wäre z.b. 9.3/2
A member function may be defined (8.4) in its class definition, in which case it is an inline member function (7.1.2), or it may be defined outside of its class definition if it has already been declared but not defined in its class definition. A member function definition that appears outside of the class definition shall appear in a namespace scope enclosing the class definition. Except for member function definitions that appear outside of a class definition, and except for explicit specializations of member functions of class templates and member function templates (14.7) appearing outside of the class definition, a member function shall not be redeclared.
-
Ok, ich sehs ein, ich hab ja auch direkt geschrieben, dass ich mir nicht sicher bin:
Phoemuex schrieb:
Bei dieser Methode bin ich mir aber nicht sicher, ob das wirklich immer so toll funktioniert, die ist mir gerade nur eingefallen
Aber weils sich (bei mir) kompilieren ließ, habe ich halt gedacht, ich poste es mal
-
Hallo nochmals,
also nach der Trennung in Header und Source Dateien klappts nun mit dem Kompilieren.
Gewundert hat es mich aber, dass ich für die pure virtual Methode destroy() auch eine Implementierung in der abstrakten Basisklasse angeben musste?!
Ich dachte, man muss nur für eine pure virtual Methode ne Implementierung angeben, nicht für alle?Ciao
-
ob eine funktion implementiert werden muss, hängt davon ab, ob sie von irgendwoher referenziert wird. prinzipiell muss keine deklarierte funktion (ob nun pure virtuell oder nicht) implementiert werden, solange sie nicht aufgerufen wird. in diesem speziellen fall ist das sogar undefiniertes verhalten. da bin ich doch erst vor ein paar tagen drüber gestolpert:
nochmal abstrakte klassen
-
OK, das leuchtet ein!
destroy() wird im Destruktor der Klasse gerufen!Die pure virtual Methoden müssen aber dennoch immer überschrieben werden, wenn die abgeleiteten Klassen nicht abstrakt sein sollen, auch wenn sie schon ne Implementierung in der Basisklasse haben, oder?