abstrakte Klassen und Vererbung
-
Hallo,
ich habe eine Klasse A und zwei abstrakte Klasse B und C.
Jetzt werden die zwei Klassen B und C implementiert, d.h. abgeleitet und die Methoden ausprogrammiert. Ich nenne sie mal neuB und neuC. Die Klasse A, B und C wissen von neuB und neuC absolut nichts!
Die Klasse A wird dann instanziert und dabei wird ein Zeiger auf eine Instanz der von B abgeleiteten Klasse übergeben, also ein Zeiger auf eine Instanz von neuB. In A werden jetzt Methoden von B aufgerufen, die also nach der abstrakten Klasse vorhanden sein müssen und in neuB ausprogrammiert sind. Das scheint gut zu funktionieren.
Ich würde jetzt aber gerne in A Instanzen von neuC an eine Methode von B übergeben. Also steht in A inetwa sowas
ptr_to_neuB->func(ptr_to_neuC);
Das geht auch noch: Ich mache mir in neuB eine Methode die mir einen Zeiger auf eine Instanz von neuC zurückgibt, denn neuB, weiß bereits etwas von neuC und kann damit eine Instanz davon erzeugen(im Gegensatz zu den abstrakten Klasse B, C und A).
Jetzt muss aber func() in der abstrakten Klasse B bereits "virtual" vorhanden sein, da die abstrakte Klasse B aber nur die abstrakte Klasse C kennt, muss da noch stehen
virtual void func(C* ptr_to_C) = 0;
Damit ich diese Funktion "func" aber in neuB ausprogrammieren kann, muss ich dort ebenfalls einen Zeiger vom Typ C als Parameter verwenden. Verwende ich in neuB die Funktionendeklaration
virtual void func(neuC* ptr_to_C) = 0;
so sagt dann der Compiler(gcc), dass die Funktion func() nicht programmiert wurde
main.cpp:28: error: cannot declare variable ‘neu_b’ to be of abstract type ‘neuB’
neuB.h:43: note: because the following virtual functions are pure within ‘neuB’:
B.h:14: note: virtual void B::func(C*)Will ich dann aber auf eine Variable zugreifen die nur in neuC existiert, aber noch nicht in C, so sagt der Compiler, dass eben C(die abstrakte Klasse) diese Variable nicht hat.
Wie schaffe ich es jetzt, dass der Compiler mitbekommt, dass ich in neuB bereits mit neuC arbeite und nicht noch immer mit C?
Ich hoffe das war jetzt nicht allzu verwirrend...
Vielen Dank,
Mighty
-
das ist ein kovariantes problem (glaub ich -.-)
basetyp B hat eine methode func die einen basetyp C akzeptiert
untertyp von B überschreibt func die diesmal einen untertyp von C akzeptiertdas ist deswegen nicht möglich weil es sowas gibt was sich untertypenbeziehung nennt die besagt mehr oder weniger "wenn Child ein untertyp von Base ist soll es überall dort wo ein Base erwartet wird auch möglich sein ein Child zu übergeben"
wenn jetzt neuB ein untertyp von B ist und neuC ein untertyp von C und es so implementiert wäre wie du es machen willst (ich hab C jetzt nicht abstrakt gemacht, soll blos demonstrieren was die problematik wäre)
class B { virtual void func(C *cPointer) = 0; } class neuB : public neuB { virtual void func(neuC *neuCPointer) { cout << neuCPointer->neuCVariable; } } class C { //irgendwas } class neuC : public C { int neuCVariable; }
dann wäre folgender code möglich
C *cPointer = new C(); B *bPointer = new neuB(); bPointer.func(cPointer); //<--- baaam
bei "baam" wird func von neuB aufgerufen, es ist erst zur laufzeit bekannt dass bPointer in wirklichkeit eine instanz von neuB ist, es kann im grunde _irgendein_ untertyp von B sein
jedenfalls wird dann entsprechend func in neuB aufgerufen, allerdings ist cPointer nicht eine instanc von neuC sondern einfach C, func würde cPointer als ein neuC behandeln und auf neuCVariable zugreifen wollen obwohl diese garnicht existiert (wir haben ja eine instanc von C und nicht neuC)
somit ist die untertypenbeziehung verletzt
was per definition zwar möglich wäre ist dass der eingabeparameter von func nicht kovariant ist sondern kontravariant (wikipedia, etc) - allerdings gibt es keine mir bekannte programmiersprache die das erlaubt, in c++ und java gilt eingabeparameter und membervariablen (deren deklarierte typen) von klassen die eine untertypenbeziehung haben müssen invariant sein (also vom selben typ)
das einzige wo eine ausnahme erlaubt ist ist bei rückgabeparametern, diese dürfen kovariant sein (func in B darf also ein C zurückgeben und func in neuB darf ein neuC zurückgeben, das ist legitim)
allerdings kannst du ein problem so lösen (func in neuB):
virtual void func(C *cPointer) { neuC *neuCPointer = (neuC *)cPointer; //do stuff with neuCPointer }
das ist natürlich keine hübsche lösung aber legitim, du musst halt eine precondition (kommentar) machen wo du explizit sagst dass die untertypen versionen von func implementieren die unter umständen parameter haben müssen welche wiederrum untertypen der typen der eingabeparameter sind welche ursprünglich von func verwendet wurden
allerdings hast du dann zwar eine vererbung - allerdings keine 100% richtige untertypenbeziehung mehr (was zwar ok ist, aber nicht gut)
du kannst auch entsprechend in func vor dem cast überprüfen ob der übergebene pointer auch wirklich zur laufzeit eine instanz von neuC ist (was du erwartest) und entsprechend die methode eine "InvalidParameterException" oder sowas (musst du dir selbst schreiben die exception klasse) werfen wenn es nicht so ist
ansonsten kann es sein dass ein anderer typ von C übergeben wird als neuC und du auf speichermüll zugreifst und bugs fabrizierst wo du später nicht draufkommst oder das programm einfach abstürzt
-
Vevusio: Danke! Ich werde jetzt einfach casten.
Wollte nur sicher gehen, dass es nicht irgendeine schöne Methode gibt um das Problem zu lösen.
Mighty