dynamic_cast, warum muss ich ihn bei einer virtuellen basis Klasse verwenden?



  • Warum muss ich einen dynamic_cast verwenden, wenn ich von einer virtuellen Basis Klasse zur child Klasse casten möchte?

    struct A{
        virtual int getType() { return 0; }
    };
    
    struct B : virtual A{
        int getType() { return 1; }
    };
    
    struct C : virtual A{
        int getType() { return 1; }
    };
    
    struct D : B, C {
    
        int getType() { return 2; }
    };
    
    int main() {
    
      A* a = new D;
    
        // funktioniert nicht
      D* myD = reinterpret_cast<D*>(a);
    
      // funktioniert
      D* myD2 = dynamic_cast<D*>(a);
    
      return 0;
    }
    

    Was ist der Grund dafür? Selbst wenn ich es weiß was für ein Type es ist, funktioniert reinterpret_cast nicht korrekt.

    Bonusefrage:
    Gibt es trotzdem eine Möglichkeit ohne dynamic_cast den ptr in ein D Objekt zu "verwandeln"?



  • static_cast



  • Definiere "funktioniert nicht"



  • Die wichtigste Frage von allen ist aber natürlich: Wieso musst du überhaupt von der Basis auf eine abgeleitete Klasse casten?



  • "funktioniert nicht" ist tatsächlich unvollständig. Aber ich denke, ich weiß wo das Problem ist. Das ist so ein Problem mit doppelter Ableitung. Das ist nicht so ganz einfach zu erklären. Aber ich versuche es mal.

    Also erst wird ein A definiert. Der hat sagen wir mal an Offset 0 seine vtable.

    Das B ist von A abgeleitet und hat seine vtable auch an Offset 0.

    Das C ist auch von A abgeleitet und hat sein vtable auch an Offset 0.

    Das D ist sowohl von B als auch C abgeleitet. Um den internen Aufbau zu verstehen, betrachten wir mal folgenden Code:

    D* d = new D;
    C* c = d;
    B* b = d;
    

    Ach ja - wir gehen mal davon aus, dass D getType im Gegensatz zum Originalcode nicht überschrieben hat.

    Ein Aufruf von c->getType() soll also die Methode der Klasse C aufrufen. b->getType() soll aber die Methode der Klasse B aufrufen. c->getType() greift also über den offset 0 auf die vtable zu und findet da eine vtable, die auf C::getType zeigt. Greife ich über den Zeiger b auf die selbe Klasse zu, findet sie aber an offset 0 die vtable, so getType auf B::getType zeigt. Also offensichtlich findet der Zeiger c auf offset 0 eine andere vtable, als der Zeiger b. Also müssen sie auf verschiedene Adressen zeigen. Also verändert die Zuweisung von D auf einen Zeiger auf C oder auf B den Wert des Zeigers. Das entspricht einem static_cast.

    reinterpret_cast nimmt aber nur den Wert und interpretiert ihn neu. Er passt den Zeigerwert also nie an. Der Code

    D* d = new D;
    C* c = reinterpret_cast<C*>(d);
    B* b = reinterpret_cast<B*>(d);
    

    hat also eine andere Bedeutung.

    In die andere Richtung, also über dynamic_cast verhält es sich ähnlich. Auch hier wird der Wert des Zeigers unter Umständen angepasst. Bei reinterpret_cast eben nicht. Daher bekommst Du bei den beiden unter Umständen unterschiedliche Ergebnisse.

    In Deinem Beispiel kann die Zuweisung des Zeigers auf D auf einen Zeiger auf A den Wert des Zeigers verändern. Das wir mit reinterpret_cast nicht rückgängig gemacht. Mit dynamic_cast aber sehr wohl.

    Welche Offsets an welcher Stelle wie angepasst werden ist vom Compiler abhängig.

    Das war so eine Kurzfassung. Ich hoffe, sie hat die Sachlage zumindest ein wenig erhellt.



  • manni66 schrieb:

    Definiere "funktioniert nicht"

    Funktioniert nicht, heißt es crasht wenn ich getType() aufrufe

    dot schrieb:

    Die wichtigste Frage von allen ist aber natürlich: Wieso musst du überhaupt von der Basis auf eine abgeleitete Klasse casten?

    Weil mir C++ es erlaubt von einer Basis Klasse zu eine abgeleiteten zu casten.

    tntnet schrieb:

    Ach ja - wir gehen mal davon aus, dass D getType im Gegensatz zum Originalcode nicht überschrieben hat.

    ..reinterpret_cast nimmt aber nur den Wert und interpretiert ihn neu. Er passt den Zeigerwert also nie an.
    ..In die andere Richtung, also über dynamic_cast verhält es sich ähnlich. Auch hier wird der Wert des Zeigers unter Umständen angepasst. ..

    Zum einen muss ich getType in D überschreiben, ansonsten bekomme ich einen compile fehler.
    Zum anderen wiedersprichst Du dich in deinen Ausagen: "passt den Zeigerwert nie an,..Auch hier wird der Wert des Zeigers unter Umständen angepasst"
    Was soll das "Auch hier"?

    reinterpret_cast passt den Zeiger nie an.
    dynamic_cast passt den Zeiger immer an.

    Ich habe das mit dem offset nicht verstanden wozu brauche ich den.
    vielleicht müsste ich erstmal verstehen wie das mit den virtuellen funktionen funktioniert.



  • Nash26 schrieb:

    dot schrieb:

    Die wichtigste Frage von allen ist aber natürlich: Wieso musst du überhaupt von der Basis auf eine abgeleitete Klasse casten?

    Weil mir C++ es erlaubt von einer Basis Klasse zu eine abgeleiteten zu casten.

    Dass man etwas tun kann heißt aber noch lange nicht, dass man es auch tun sollte...



  • int main()
    {
        int* pi = new int;
        std::string* ps = reinterpret_cast<std::string*>(pi);
    
        std::cout << ps->c_str() << std::endl; // WTF? Crash? C++ sucks!
    }
    


  • Nash26 schrieb:

    dot schrieb:

    Die wichtigste Frage von allen ist aber natürlich: Wieso musst du überhaupt von der Basis auf eine abgeleitete Klasse casten?

    Weil mir C++ es erlaubt von einer Basis Klasse zu eine abgeleiteten zu casten.

    Da hast Du wohl was falsch verstanden. Man muss nicht alles tun, was erlaubt ist.



  • Nash26 schrieb:

    Warum muss ich einen dynamic_cast verwenden, wenn ich von einer virtuellen Basis Klasse zur child Klasse casten möchte?

    Schizophren.
    Die andere Richtung ist auch nicht viel besser.
    Beides deutet natürlich auf Designfehler hin.



  • Trotz all dem

    - Mehrfachvererbung
    - polymorphic down cast

    In der Literatur zu Sowftware Designs sind diese Dinge als designteschnische Folgefehler beschrieben.

    D.h. stolperst du Du ueber diese Probleme, hasst Du im Design hoechstwahrscheinlich nen Fehler etwas früher gemacht.

    Also dynamic_cast und mehrfachvererbung sollt man immer als fettes rotes Ausrufezeichen im Quellcode betrachten, als marker das man das Design da nochmals überdenken sollte.
    2 solche aufrufezeichen sind dann schon hart ^^

    Weil mir C++ es erlaubt von einer Basis Klasse zu eine abgeleiteten zu casten.

    upcast, also child zu basis, ist durchaus gaengig und gewollt. das ist kein Problem. Das die Basisklasse immer weniger eigenschaften als wie die Ableitung hat. die Ableitung fügt also eigenschaften zur Basis hinzu.

    downcast, ist problematisch, weil du vom typ mit weniger eigenschaften auf nen Typ mit mehr eigenschaften switchen willst. du brauchst also weitere informationen um dir sicher zu sein, das nach dem cast der zeiger auch noch benutzbar ist.
    Das laeuft eigentlich dem konzept/Gedanken der Polymorphie zuwieder.

    Polymorphy heisst eigentlich nichts anderes, das du Objecten unterschiedlicher funktion an irgendeiner stelle gleichbehandeln willst ... willst du dann doch wieder unterschiede(downcast), warum dann polymorphie ?

    Kann es sein das du von ner anderen Sprache kommst, wo "ableiten" nen anderen Context hat und viel mehr verwendet wird ("Alles ist eine klasse", Java z.b.) ?

    Also Desing c++ technisch noch mal ueberdenken ...

    Ciao ...



  • ohh man oh man, eigentlich können alle Antworten bis auf die von tntnet gelöscht werden.

    Wo habe ich geschrieben das ich so eine Klassenstruktur verwende?
    Das so etwas kein gutes Design ist, sollte klar sein.

    Aber danach habe ich auch nicht gefragt, darum ging es in meiner Frage auch nicht.

    Die Frage war IMHO klar gestellt:

    Warum muss ich einen dynamic_cast verwenden, wenn ich von einer virtuellen Basis Klasse zur child Klasse casten möchte?
    

    Die Frage richtet sich zugegebenermaßen eher an Hardcore C++ Entwickler die auch auf unterster Ebene wissen was abgeht.



  • Die Antwort auf diese Frage ist einfach: Das stimmt nicht, static_cast geht auch...


  • Mod

    dot schrieb:

    Die Antwort auf diese Frage ist einfach: Das stimmt nicht, static_cast geht auch...

    Keineswegs. Ist die virtuelle Basis polymorph, kann dynamic_cast für Downcasts benutzt werden. Ist sie nicht polymorph, ist ein Downcast schlicht nicht möglich.



  • camper schrieb:

    dot schrieb:

    Die Antwort auf diese Frage ist einfach: Das stimmt nicht, static_cast geht auch...

    Keineswegs. Ist die virtuelle Basis polymorph, kann dynamic_cast für Downcasts benutzt werden. Ist sie nicht polymorph, ist ein Downcast schlicht nicht möglich.

    Stimmt, sry, hatte übersehen dass es sich um eine virtuelle Basis handeln soll. Der Grund ist natürlich, dass der Compiler in dem Fall zur Compilezeit nicht wissen kann, wie der Pointer zu verschieben ist und daher auf die Informationen in der vtable des base class subobject angewiesen ist...



  • camper schrieb:

    dot schrieb:

    Die Antwort auf diese Frage ist einfach: Das stimmt nicht, static_cast geht auch...

    Keineswegs. Ist die virtuelle Basis polymorph, kann dynamic_cast für Downcasts benutzt werden. Ist sie nicht polymorph, ist ein Downcast schlicht nicht möglich.

    Ich würde hier eher sagen:

    Ist die virtuelle Basis polymorph, MUSS dynamic_cast für Downcasts benutzt werden.
    

    Es sei denn du weißt einen weg wie es auch ohne dynamic_cast geht...

    [quote]
    Der Grund ist natürlich, dass der Compiler in dem Fall zur Compilezeit nicht wissen kann, wie der Pointer zu verschieben ist und daher auf die Informationen in der vtable des base class subobject angewiesen ist...[quote]
    Was hat es mit den pointer verschieben auf sich?
    Wie funktioniert ein cast an sich?



  • Nash26 schrieb:

    camper schrieb:

    dot schrieb:

    Die Antwort auf diese Frage ist einfach: Das stimmt nicht, static_cast geht auch...

    Keineswegs. Ist die virtuelle Basis polymorph, kann dynamic_cast für Downcasts benutzt werden. Ist sie nicht polymorph, ist ein Downcast schlicht nicht möglich.

    Ich würde hier eher sagen:

    Ist die virtuelle Basis polymorph, MUSS dynamic_cast für Downcasts benutzt werden.
    

    Es sei denn du weißt einen weg wie es auch ohne dynamic_cast geht...

    Du hast das "kann" falsch verstanden. "Kann" in dem Sinn, dass es dann überhaupt eine Möglichkeit gibt. Wenn die virtuelle Basis nämlich nicht polymorph ist, dann geht es gar nicht. Also mit keinem der verfügbaren Casts.



  • Was hat es mit den pointer verschieben auf sich?

    Solange Du das Wissen nicht anwendest, um Code zu schreiben, ist Neugier natuerlich eine gute Sache ! 😃

    Aber detailierte und generische Antworten darueber zu finden, wird schwer, weil das schon sehr in die compiler internas geht.

    Was du bereits wissen solltest:

    "Normale" Klassen (ohne virtuelle Methoden) werden nur durch ihre Member definiert. D.h. die verhalten sich wahrscheinlich wie stucts ...
    du kannst mal ne struct und ne class definieren mit gleicher Member-Signatur ! ... dann mal Instanzen boese casten, wirst sehen das wird in den meisten fallen funktionieren !
    D.h. der Klassen-Zeiger zeigt auf den Anfang der Member Daten, wo auch immer "Anfang" auf dem System definiert ist.

    Bei virtuellen Klassen kommen die Methodenpointer hinzu, sprich die vtable.
    Wo die liegt, ist nicht definiert.

    Geruechten zu folge liegt sie bei vielen Compilern vor ! den Membern.
    Bei linearen ableitungen ist es damit recht einfach. Kommen member hinzu, haengt man die hinter die Liste ... Kommen methoden hinzu, haengt man die vornedran.
    Beim casten eines zeigers aendert sich dann nur der Typ, nicht der wert ... d.h. man koennte reinterpret cast verwenden !
    und man koennte die zeiger vergleichen, um zu wissen ob es sich um die gleiche Instanz handelt ... Das wird impliziet gar ned so selten verwendet.

    Mehrfachvererbung wird dann eklig ... kannst dir vorstellen warum ?
    X ist abgeleitet von A, B und C ...
    wie koennte das der compiler aufbauen ?
    A B und C kann er nachtraglich nicht anpassen, so das sie sich einfach zusammenfuegen lassen ...

    Wenn er die VMethoden anlegt nach C-B-A, dann die Member, koennte beim cast der A zeiger bleiben ... beim B muesste er den pointer so verschieben, das die VMethoden von B relativ wieder zum pointer passen ...

    Was macht er wenn die klassen nicht nur abstract sind, sondern auch selber member haben ....
    Dann muesste er CMethoden-CMember,BMethoden-BMember,AMethoden-AMember aufbauen ... auch hier muesste er den pointer verschieben beim cast ...

    vielleicht legt er auch mehrere redundante vtables an, nur damit die geometry stimmt ?

    Das der zeiger nachm cast gleichbleibt zu jeder klasse , wird schwierig, also der vergleich auf raw ebene geht dann nimmer! du kannst nur hoffen dass er nen dynamic_cast(implizieten upcast) vorher macht ....

    Wie gesagt, wie die compiler das machen, ist nicht vorgeschrieben, es muss nur funktionieren ... deswegen ist eh vieles Spekulation. Die einzige Wahrheit aus der Sache ist, man soll sich auf das Verlassen was spezifiziert ist, und den Rest einfach ignoerieren, sonst schreibt man ganz schnell code mit undefinierten Verhalten ...

    Ciao ...



  • Wenn dich dieses Thema interessiert dann schau dir doch mal
    Inside the C++ Object Model | ISBN: 0201834545
    an. Da werden solche Sachen erklärt...



  • RHBaum schrieb:

    Bei virtuellen Klassen kommen die Methodenpointer hinzu, sprich die vtable.
    Wo die liegt, ist nicht definiert.

    Geruechten zu folge liegt sie bei vielen Compilern vor ! den Membern.

    Nein. Der VTable liegt irgendwo. 1x pro Klasse. Und als erstes Member gibt's in jedem Objekt dann einen (!) Zeiger (!) auf den VTable.

    Bei linearen ableitungen ist es damit recht einfach. Kommen member hinzu, haengt man die hinter die Liste ... Kommen methoden hinzu, haengt man die vornedran.

    linear? WTF? Im Gegensatz zu ... was? Quadratischer Ableitung?

    Und nein. Methoden hängt man auch hinten dran. Und zwar hinten an den VTable für die abgeleitete Klasse. Und das erste Member bleibt weiterhin einfach ein Zeiger auf den VTable. Deswegen verschiebt sich da in den Objekten auch nix.

    Mehrfachvererbung wird dann eklig ... kannst dir vorstellen warum ?
    X ist abgeleitet von A, B und C ...
    wie koennte das der compiler aufbauen ?

    Alles noch kein Problem. Mehrfachvererbung funktioniert wunderbar ohne dynamic_cast oder dergleichen.

    Eklig wird wirklich erst virtuelle Vererbung. Und um zu erklären wieso braucht man nichtmal VTable Zeiger oder dergleichen.

    ----

    Auch wenn ich mich damit wieder unbeliebt mache: wenn man andere belehren will, sollte man erstmal selbst verstanden haben was abgeht.


Log in to reply