Probleme beim Zugriff auf Subklassen bei Mehrfachvererbung
-
Hallo,
ich habe gerade ein Problem mit einer Klasse die mehrfach irbt. Ich hoffe ich kann im folgenden mein Problem verständlich beschreiben...
Im wesentlichen geht es um drei Klassen:
class A = 0; (Klasse ist abstrakt)
class B;
class C : public A, public B;Nun bekomme ich an einer anderen Stelle eine Referenz auf ein Objekt obj von Typ A. Ich weiß aber sicher, dass es sich um ein Objekt handelt, welches von A und von B erbt (also zB ein Objekt vom Typ C). Nun würde ich gerne auf die Methoden (methB() ) der Klasse B zugreifen.
Der einfachste Versuch:
obj.methB()
liefert den Fehler: methB is not a member of class ANun habe ich eine Weile mit verschiedenen Cast Varianten herumgespielt (in habe nicht direkt eine Referenz auf obj, sondern einen boost::shared_ptr auf dieses Objekt), die leider alle nicht erfolgreich waren und im wesentlichen immer folgenden Fehler liefern:
cannot convert from 'boost::shared_ptr<T>' to 'boost::shared_ptr<T>'
3> with
3> [
3> T=A ]
3> and
3> [
3> T=B
3> ]
3> Conversion requires a constructor or user-defined-conversion operator, which can't be used by const_cast or reinterpret_castHier hatte ich versucht meinen shared_ptr<A> in einen shared_ptr<B> umzuwandeln.
Was als Alternative leider nicht geht ist ein downcast auf C, da das übergebene Objekt nicht zwangsläufig Klasse C ist. Sicher ist aber, dass es von A und von B erbt.
Bin da momentan etwas ratlos.... Hoffe ich habe das Problem ausreichend verständlich geschildert, um hier im Forum Hilfe zu finden.
-
Magst du ein klein bischen Code zeigen?
-
War mir nicht sicher, ob Originalcode nicht eher verwirrt. Kann aber natürlich Codeabschnitte einfügen:
Klasse A:class OneFactorAffineModel : public OneFactorModel, public AffineModel { ...virtuelle Klasse }Klasse B:
class TermStructureConsistentModel : public virtual Observable { public: TermStructureConsistentModel( const Handle<YieldTermStructure>& termStructure) : termStructure_(termStructure) {} const Handle<YieldTermStructure>& termStructure() const { return termStructure_; } private: Handle<YieldTermStructure> termStructure_; };Auf die Methode Termstructure würde ich gerne zugreifen.
Klasse C:
class HullWhite : public Vasicek, public TermStructureConsistentModel { ... Vasicek erbt von OneFactorAffineModel }Und so würde ich es gerne verwenden:
private: ModelYieldTermStructure modelYc_; boost::shared_ptr<OneFactorAffineModel> model_; public: CouponPayoffGenerator( boost::shared_ptr<OneFactorAffineModel>& model ) : model_(model), modelYc_(model, (reinterpret_cast<boost::shared_ptr<TermStructureConsistentModel> >(model)->termStructure()->dayCounter()) { }Um meine ModelYieldTermStructure modelYc_ aufzubauen, brauche ich sowohl das OneFactorAffineModel, als auch die Methode in der Klasse TermStructureConsistentModel.
Hilft das?
-
Also ich persönlich weiß es nicht :(. Müssen wir warten bis die guten Leute heute Abend da sind :).
-
Ich glaube nicht, dass das mit einem einfachen cast überhaupt geht, da der compiler da in ganz ganz böse Probleme mit dem Datenlayout der Klassen kommen würde.
Würde behaupten du musst auf jedes mögliche C (bzw D,E,F...) prüfen und dann entsprechend casten. Oder du entflechtest deine Vererbung etwas.
-
Wie Dir Dein Compiler schon sagt: Er kann zwei shared pointer unterschiedlichen Typs nicht ineinander casten, weil er den dafür nötigen Konstruktor nicht erzeugen bzw. finden kann.
Jetzt bin ich sicherlich nicht der Experte für shared_pointer aber: Das geht bei shared_ptr deswegen nicht, weil ein shared_ptr<A> ein komplett anderer Typ ist als ein shared_ptr<C>, d.h. es gibt hier keine Vererbungshierarchie innerhalb der gecastet werden kann. Du musst also, wenn Du das unbedingt so machen willst, einen entsprechenden Konstruktor bereit stellen.
Alternative: Einen dynamic_cast<C*>(A*) kann man bei vanilla Zeigern verwenden, um in einer Hierarchie zu casten. Du musst dann auf nullptr prüfen. Dann verlierst Du aber den Vorteil, den Dir shared_ptr<> bieten. Hinzu kommt, dass Du pro subtyp einen if (dynamic_cast<C*>(a) == nullptr) schreiben musst.
Vom Design Deiner Hierarchie vermute ich hier aber ein Problem, da das eigentlich auf eine Verletzung des Liskov'schen Substitutionsprinzip hin deutet. Du solltest Dir also überlegen, ob Dein Designansatz nicht anders formuliert werden kann.
EDIT: Eine weitere Alternative ist, dass Du eine "Zwischenklasse" Z einführst, die einfacht nur public von A und B erbt. Das ist dann Deine Klasse mit der Du arbeitest. Dadurch wird das Design aber nicht wirklich besser.
-
Zwischen shared_ptr'ern kann man sehr wohl casten, dafür gibts freie Funktionen.
-
@PI: Danke für den Hinweis
:template <class Ty, class Other>
shared_ptr<Ty> dynamic_pointer_cast(const shared_ptr<Other>& sp);Wieder was gelernt.
Dennoch: Ich würde das Design überdenken.
-
ogni42 schrieb:
Wie Dir Dein Compiler schon sagt: Er kann zwei shared pointer unterschiedlichen Typs nicht ineinander casten, weil er den dafür nötigen Konstruktor nicht erzeugen bzw. finden kann.
Jetzt bin ich sicherlich nicht der Experte für shared_pointer aber: Das geht bei shared_ptr deswegen nicht, weil ein shared_ptr<A> ein komplett anderer Typ ist als ein shared_ptr<C>, d.h. es gibt hier keine Vererbungshierarchie innerhalb der gecastet werden kann. Du musst also, wenn Du das unbedingt so machen willst, einen entsprechenden Konstruktor bereit stellen.
Alternative: Einen dynamic_cast<C*>(A*) kann man bei vanilla Zeigern verwenden, um in einer Hierarchie zu casten. Du musst dann auf nullptr prüfen. Dann verlierst Du aber den Vorteil, den Dir shared_ptr<> bieten. Hinzu kommt, dass Du pro subtyp einen if (dynamic_cast<C*>(a) == nullptr) schreiben musst.
Vom Design Deiner Hierarchie vermute ich hier aber ein Problem, da das eigentlich auf eine Verletzung des Liskov'schen Substitutionsprinzip hin deutet. Du solltest Dir also überlegen, ob Dein Designansatz nicht anders formuliert werden kann.
EDIT: Eine weitere Alternative ist, dass Du eine "Zwischenklasse" Z einführst, die einfacht nur public von A und B erbt. Das ist dann Deine Klasse mit der Du arbeitest. Dadurch wird das Design aber nicht wirklich besser.
Danke für alle bisherigen Antworten!
Das Liskovsches Substitutionsprinzip kannte ich bisher nicht, daher musste ich mich in Wiki schlau machen http://de.wikipedia.org/wiki/Liskovsches_Substitutionsprinzip. Da steht ja gerade (so verstehe ich das), dass durch Vererbung mehrerer Objekte das Prinzip ausgehebelt werden kann. Das wäre ja hier dann so ein Fall.
Mein Problem ist, dass ich meinen Code in eine bestehende umfangreiche Bibliothek "quetschen" muss und möglichst wenig (am besten garnichts) am Status Quo ändern sollte. Daher funktioniert die Variante mit der Zwischenklasse (was natürlich eine elegante Lösung wäre) nicht.
Prinzipiell gibt es für shared_ptr cast Operatoren (einen reinterpret Operator habe ich jedoch nicht gefunden). Die existierenden haben mich bei diesem Problem nicht weitergebracht.
Die Alternative nach allen möglichen Subtypen abzufragen, ist natürlich nicht elegant, aber scheint mir für mein Problem bisher die beste Lösung. Bin ich selbst nicht drauf gekommen
Danke dafür!Vielleicht gibt es ja aber doch noch einen anderen Ansatz?
-
Also der
template <class Ty, class Other> shared_ptr<Ty> dynamic_pointer_cast(const shared_ptr<Other>& sp);sollte eigentlich gehen. Da musst Du dann aber auch auf nullptr prüfen und bekommst eine if-Kaskade für alle möglichen Subklassen, aber immerhin kannst Du dann shared_ptr benutzen.
-
ogni42 schrieb:
Also der
template <class Ty, class Other> shared_ptr<Ty> dynamic_pointer_cast(const shared_ptr<Other>& sp);sollte eigentlich gehen. Da musst Du dann aber auch auf nullptr prüfen und bekommst eine if-Kaskade für alle möglichen Subklassen, aber immerhin kannst Du dann shared_ptr benutzen.
Wie gesagt, die shared_ptr cast Operatoren haben leider ähnliche Fehler geliefert wie oben. Könnte das morgen nochmal nachstellen und die genaue Fehlermeldung reinstellen.
Bin jetzt leider bis zum späten Abend offline...
-
Zu meiner Überraschung gehen cross-casts tatsächlich.
Das kompiliert und funktioniert bei mir:#include <memory> #include <iostream> using namespace std; class A { public: virtual ~A(){};}; class B { public: void doSomething() { cout<<"test"<<endl; } }; class C : public A, public B {}; int main() { shared_ptr<A> a(new A); dynamic_cast<B*>(a.get())->doSomething(); }beachte den Umweg über shared_ptr::get()
Da kann man auch diskutieren, ob das ideal ist, aber da man den pointer von get() direkt verwendet seh ich da eigentlich kein Problem.
-
Uff, der code war natürlich absoluter Schwachsinn. Dynamic_cast returnt da ja einfach 0, und da ich auf keine Variable zugreift funktioniert zufälligerweise der function call ohne dass es einem um die Ohren fliegt.
Prinzip stimmt aber trotzdem, hoffentlich diesmal richtiger code:
#include <memory> #include <iostream> #include <string> using namespace std; class A { public: virtual ~A(){};}; class B { public: string test; B() { test = "test"; } void doSomething() { cout<<test<<endl; } }; class C : public A, public B {}; int main() { shared_ptr<A> a(new C); //wenn das hier nur ein A wäre, kann er das natürlich nicht in B casten. B* b = dynamic_cast<B*>(a.get()); if(b) { b->doSomething(); } else { cout<<"cast klappt nicht :("<<endl; } }