Wieso ändert sich Zeiger?
-
Hm, also ich habe ein konkretes Problem, das ich nicht so ganz raffe. Es sind recht viele Klassen beteiligt, ich versuch es mal auf den Kern zu vereinfachen:
struct Owner { virtual ~Owner() { } virtual void onCreated(A* a) = 0; }; struct A { Owner* mOwner; A(Owner* o) : mOwner(o) { } virtual ~A() { } }; struct B : public A { private: virtual void onDestroy() = 0; virtual void onReset() = 0; public: B(Owner* o) : A(o) { } virtual ~B() {} }; struct C : public Foo, public B { private: virtual void onDestroy() { } virtual void onReset() { if(mOwner) mOwner->onCreated(this); } public: C(Owner* o) : Foo(), B(o) { } }; class Manager : public ManagerBase, public Owner { C* mC; void methode() { C* = new C(this); } virtual void onCreated(A* a) { C* c = dynamic_cast<C*>(a); // hier ist c NULL. Wieso? Hinter dem a sollte doch ein C stecken? } };So, der Code sieht übel aus, is aber eigentlich simpel. Ich habe die Vererbungshierachie A, B, C. C erbt zusätzlich von einem Foo. Ein A HAT einen Owner (damit auch B und C).
Im Manager erzeuge ich jetzt ein C und übergebe this (Sprich den Manager, der ja ein Owner ist, an C). Später wird dann C::onReset() aufgerufen, was dafür sorgt, dass Manager::onCreated(A* a) aufgerufen wird. Dabei wird this übergeben, was ja ein C sein sollte.
Nur wenn ich jetzt in Manager::onCreated(A* a) das a auf ein C caste, dann ist es immer NULL! Wie kann das sein?
-
Hallo thissa,
ich denke, da macht dir die Mehrfachvererbung "einen Strich durch die Rechnung".
Bei reiner Einfachvererbungstruct C : public B // ohne Foosollte der dynamic_cast ja funktionieren.
Du könntest aber mal virtuelle Vererbung ausprobieren:
struct B : public virtual Adenn dann gibt es bei Mehrfachvererbung auch nur genau ein A Objekt - ich weiß aber nicht, ob dies dann beim dynamic_cast als eineindeutig interpretiert wird.
Und ich nehme mal an, daß du die Klasse "Foo" nicht auch von A hast erben lassen, oder? Weil sonst gäbe es ja zwei A Objekte innerhalb von C, und dann wäre der dynamic_cast nicht eindeutig und daher wird dann wohl 0 (Null) zurückgegeben.Aber wahrscheinlich kann dir hier im Forum nur camper wirklich eine korrekte Antwort geben...
-
Das sieht soweit problemlos aus. Der Fehler dürfte woanders zu suchen sein. Hier wäre sogar ein static_cast möglich. Vielleicht sind ja auch irgendwelche Compilereinstellungen schuld. Ist denn RTTI aktiviert?
-
camper schrieb:
Das sieht soweit problemlos aus. Der Fehler dürfte woanders zu suchen sein. Hier wäre sogar ein static_cast möglich. Vielleicht sind ja auch irgendwelche Compilereinstellungen schuld. Ist denn RTTI aktiviert?
Ich stimme dir absolut zu, dass der Fehler wo anders liegen sollte.
Da ich eigentlich selten mit meinen Compilereinstellungen rumspiele und somit wohl RTTI immer aktiviert ist, stelle ich mir gerade folgende Frage:
Braucht man für einen dynamic_cast RTTI?
-
Ja. Für einen dynamic_cast muss der Typ (soweit ich weiß) zur Laufzeit ermittelt werden. Das ist genau das, wofür RTTI steht. Runtime Type Information.
-
camper schrieb:
Werner Salomon schrieb:
Die Werte der Zeiger sind verschieden. Die Zeiger zeigen aber auf das identische Objekt - also sind sie gleich.
Es handelt sich um Zeiger mit verschiedenen Typen, die können naturgemäß niemals auf ein und dasseble Objekt zeigen.
doch doch (s.u.)
camper schrieb:
struct A {}; struct B : A {}; struct C : A {}; struct D : B, C {}; D x; C* c = &x; B* b = &x; A* a = c; assert( a == b ); // schlägt fehlHier sind drei Zeiger a,b und c. Alle haben verschieden Typen und alle zeigen auf das eine und einzige Objekt x - auf was denn sonst. Oder haben wir zu dem Ausdruck 'Objekt' unterschiedliche Ansichten?
camper schrieb:
//... assert( a == b ); // schlägt fehlwürde gut gehen mit
struct A { virtual ~A() {} };Gruß
Werner
-
Werner Salomon schrieb:
Hier sind drei Zeiger a,b und c. Alle haben verschieden Typen und alle zeigen auf das eine und einzige Objekt x - auf was denn sonst.
c ist ein C* und zeigt damit nicht auf ein D (also auf x) sondern auf ein C - und zwar auf das C-Subobjekt von x, d.h. x::C, nicht auf x selbst. Analoges gilt für b, a wiederum zeigt auf x::C::A, nicht auf x selbst. Das ist eine Frage der Objektidentität, und Subobjekte sind nunmal nicht identisch mit dem Objekt selbst.
-
Werner Salomon schrieb:
Hier sind drei Zeiger a,b und c. Alle haben verschieden Typen und alle zeigen auf das eine und einzige Objekt x - auf was denn sonst. Oder haben wir zu dem Ausdruck 'Objekt' unterschiedliche Ansichten?
Zutreffend wäre zu sagen, dass alle drei Zeiger in das Objekt zeigen, nämlich jeweils auf ein bestimmtes Subobjekt von x.
Werner Salomon schrieb:
camper schrieb:
//... assert( a == b ); // schlägt fehlwürde gut gehen mit
struct A { virtual ~A() {} };Das hilft nicht, aber wichtiger ist noch, dass du zustimmst, dass der Vergleich fehlschlägt.
a == b vergleicht eben nicht Adressen - das wäre nur der Falle wenn void-Zeiger oder Zeiger gleichen Typs vergleichen werden.
a == b im Zusammenhang mit Klassenzeigern hingegen stellt dies die Frage, ob ob einer der Zeiger auf ein Bassenklassensubobjekt des Objektes zeigt, auf das der anderere Zeiger verweist (und falls die Zeiger nicht verwandt sind, oder die Basisklassenkonvertierung mehrdeutig ist oder Zugriffsrechte fehlen, dann ist der Ausdruck ill-formed).
Ein echter Adressvergleich im zweiten Fall ist nur möglich, wenn der am weitesten abgeleitete Typ bekannt ist, oder beide Klassen polymorph sind, in letzterem Fall:dynamic_cast<const void*>(a) == dynamic_cast<const void*>(b)
-
314159265358979 schrieb:
Ja.
thx
314159265358979 schrieb:
Für einen dynamic_cast muss der Typ (soweit ich weiß) zur Laufzeit ermittelt werden.
Dessen bin ich mir bewusst.
314159265358979 schrieb:
Das ist genau das, wofür RTTI steht. Runtime Type Information.
Da stellte sich mir die Frage, welche Ausprägung diese Runtime Type Information haben...
-
XSpille schrieb:
Da stellte sich mir die Frage, welche Ausprägung diese Runtime Type Information haben...
Für die Zwecke von dynamic_cast wird Information über den kompletten Vererbungsbaum einer Klasse benötigt. Die einfachste Möglichkeit dürfte es sein, dem type_info-Objekt für jede Klasse noch extra Information mitzugeben (jeweils ein Zeiger auf das type_info-Objekt jeder Basisklase + Information ob die Vererbung public oder virtuell ist). Wenn dann noch jede vtable einen Zeiger auf das zugehörige type_info-Objekt hat, könnte man sich dynamic_cast selber schreiben. Möglich, dass es effizientere Implementationen gibt.
-
camper schrieb:
XSpille schrieb:
Da stellte sich mir die Frage, welche Ausprägung diese Runtime Type Information haben...
Für die Zwecke von dynamic_cast wird Information über den kompletten Vererbungsbaum einer Klasse benötigt. Die einfachste Möglichkeit dürfte es sein, dem type_info-Objekt für jede Klasse noch extra Information mitzugeben (jeweils ein Zeiger auf das type_info-Objekt jeder Basisklase + Information ob die Vererbung public oder virtuell ist). Wenn dann noch jede vtable einen Zeiger auf das zugehörige type_info-Objekt hat, könnte man sich dynamic_cast selber schreiben. Möglich, dass es effizientere Implementationen gibt.
Danke Camper! Also eine Vorstellung wie das funktioniert, hatte ich.
Allerdings hatte ich pro Klasse eine komplette vtable mit allen virtuellen Funktionen erwartet. Vor den Funktionen einer Schnittstellendefinition einen Zeiger auf die exakte Klasse hat. Also quasi mehrere subvtables.
Die exakte Klasse hat dann (z.B.) am Anfang eine Liste von Paaren (interne id, Zeiger auf subvtables).Gruß,
XSpille
-
XSpille schrieb:
Allerdings hatte ich pro Klasse eine komplette vtable mit allen virtuellen Funktionen erwartet. Vor den Funktionen einer Schnittstellendefinition einen Zeiger auf die exakte Klasse hat. Also quasi mehrere subvtables.
Die exakte Klasse hat dann (z.B.) am Anfang eine Liste von Paaren (interne id, Zeiger auf subvtables).Die Zielklasse muss nicht polymorph sein (cross-cast), also hast du nicht in jedem Fall eine vtable dafür.
-
camper schrieb:
XSpille schrieb:
Allerdings hatte ich pro Klasse eine komplette vtable mit allen virtuellen Funktionen erwartet. Vor den Funktionen einer Schnittstellendefinition einen Zeiger auf die exakte Klasse hat. Also quasi mehrere subvtables.
Die exakte Klasse hat dann (z.B.) am Anfang eine Liste von Paaren (interne id, Zeiger auf subvtables).Die Zielklasse muss nicht polymorph sein (cross-cast), also hast du nicht in jedem Fall eine vtable dafür.
Hatte ich nicht berücksichtigt...
Aber sollte doch trotzdem annähernd so gehen.
Dann halt zusätzlich zu den Zeigern auf die vtable Cast-Informationen (Offset-Verschiebungen).
Und beim Cast kennt man ja den Ziel-Typen und folglich weiß man, was für Infos berücksichtigt werden müssen.
-
camper schrieb:
Werner Salomon schrieb:
camper schrieb:
//... assert( a == b ); // schlägt fehlwürde gut gehen mit
struct A { virtual ~A() {} };Das hilft nicht, aber wichtiger ist noch, dass du zustimmst, dass der Vergleich fehlschlägt.
Ja - dem stimme ich natürlich zu. Und inzwischen weiß ich auch, was Du und pumuckl meinen.
Alles was Ihr schreibt ist erstens richtig und zweitens war mir das alles bekannt. Ich habe das Zeug ja lange genug beim Debuggen im Speicher gesehen. Nur ich hatte was anderes gemeint, da unsere Objektbegriffe verschieden sind - interessant wie man so aneinander vorbei reden kann!
Wenn ich von 'Objekt' schreibe, meine ich das "ganze" 'x' (vom Typ 'D' aus dem Beispiel von Camper) - also das, was z.B. Booch darunter versteht. Das es da Subobjekte gibt, auf die die Adressen der Zeiger verweisen, hatte ich als selbstverständlich vorausgesetzt - und die meinte ich explizit nicht.
Gruß
Werner