dynamic_cast böse



  • Es wird ja immer gesagt, dass dynamic_cast auf schlechtes Design hinweist usw.

    Habt ihr große Projekte mit deutlich mehr als 10000 Zeilen Code und keinen einzigen dynamic_cast? Wenn ja, habt ihr irgendwas ähnliches gebaut, wie z.B. getObjectType?



  • ButterFisch schrieb:

    Wenn ja, habt ihr irgendwas ähnliches gebaut, wie z.B. getObjectType?

    Das wäre doch genau der gleiche Designfehler? Das Problem, ist ja nicht dynamic_cast an sich, sondern eben das Design das dazu führt, dass man einen dynamic_cast braucht. dynamic_cast ist nur ein mögliches Symptom des Problems...



  • ButterFisch schrieb:

    Wenn ja, habt ihr irgendwas ähnliches gebaut, wie z.B. getObjectType?

    Ja.



  • Hm.. wie baut ihr dann z.B. in einer Simulation (a.k.a. Spiel) ein System auf, in dem ja irgendwo alle Objekte bekannt sein müssen? Da scheint mit dynamic_cast erst mal die einfachste Lösung, um herauszubekommen, ob ein Objekt z.B. kollidieren kann.



  • dot schrieb:

    ButterFisch schrieb:

    Wenn ja, habt ihr irgendwas ähnliches gebaut, wie z.B. getObjectType?

    Das wäre doch genau der gleiche Designfehler? Das Problem, ist ja nicht dynamic_cast an sich, sondern eben das Design das dazu führt, dass man einen dynamic_cast braucht. dynamic_cast ist nur ein mögliches Symptom des Problems...

    Gut bemerkt, hat aber nichts mit meiner Frage zu tun. Ich will wissen, ob ihr wirklich ohne dynamic_cast oder Ersatzsysteme auskommt.

    Es wäre auch noch interessant, wenn ihr angeben könnt, wieviel Leute ungefähr an dem Projekt arbeiten.



  • ButterFisch schrieb:

    Gut bemerkt, hat aber nichts mit meiner Frage zu tun. Ich will wissen, ob ihr wirklich ohne dynamic_cast oder Ersatzsysteme auskommt.

    Bisher: Ja. Ausname: Wenn das Framework/eine Library einen dazu zwingt.
    Leute: Nur ich.



  • cooky451 schrieb:

    Hm.. wie baut ihr dann z.B. in einer Simulation (a.k.a. Spiel) ein System auf, in dem ja irgendwo alle Objekte bekannt sein müssen? Da scheint mit dynamic_cast erst mal die einfachste Lösung, um herauszubekommen, ob ein Objekt z.B. kollidieren kann.

    Die Antwort dazu wird double dispatch sein, können wir bei meiner Frage bleiben. 😃



  • cooky451 schrieb:

    Hm.. wie baut ihr dann z.B. in einer Simulation (a.k.a. Spiel) ein System auf, in dem ja irgendwo alle Objekte bekannt sein müssen? Da scheint mit dynamic_cast erst mal die einfachste Lösung, um herauszubekommen, ob ein Objekt z.B. kollidieren kann.

    Wo liegt das Problem? Für Kollisionen wäre die Antwort in der Tat double dispatch.



  • cooky451 schrieb:

    Hm.. wie baut ihr dann z.B. in einer Simulation (a.k.a. Spiel) ein System auf, in dem ja irgendwo alle Objekte bekannt sein müssen? Da scheint mit dynamic_cast erst mal die einfachste Lösung, um herauszubekommen, ob ein Objekt z.B. kollidieren kann.

    Ich würde lieber alle Objekte, die kollidieren können, irgendwo mit callback registrieren und dann zentral verwalten. Spart rtti und braucht auch keinen double dispatch.
    @topic: Ich hab zumindest ein selbst gebautes typeid verwendet (das eingebaute ist wirklich lahm, weils auch zwischen dlls funktioniert), aber nicht für die Logik an sich sondern für das framework.



  • cooky451 schrieb:

    Hm.. wie baut ihr dann z.B. in einer Simulation (a.k.a. Spiel) ein System auf, in dem ja irgendwo alle Objekte bekannt sein müssen? Da scheint mit dynamic_cast erst mal die einfachste Lösung, um herauszubekommen, ob ein Objekt z.B. kollidieren kann.

    Die Frage ist ohne ein genaueres Szenario nicht sehr genau beantwortbar. Der relevante Allgemeinplatz ist: Man hält kollidierbare Objekte von anderen getrennt und sorgt so dafür, dass andere erst gar nicht in eine Kollisionsprüfung rutschen können.

    Letztlich sind die Vorbehalte gegen dynamic_cast aber nur in einem objektorientierten Kontext zu verstehen. Die Notwendigkeit, den genauen Typ eines Objektes zur Laufzeit herausfinden zu müssen, widerspricht dem objektorientierten Gedanken der Kapselung völlig - der Client-Code soll die Implementationsdetails der Objekte, mit denen er arbeitet, nicht kennen müssen, sondern lediglich ihre Schnittstelle.

    Folgt man einem anderen Paradigma, mag es sein, dass dynamic_cast in sauberem Design sinnvoll eingesetzt werden kann. Mir fällt jetzt auf die Schnelle kein solcher Kontext ein, was aber damit zusammenhängen wird, dass ich, von Ausflügen ins Funktionale mal abgesehen, bislang nur im objektorientierten Kontext mit Klassen gearbeitet habe (die man für dynamic_cast nun mal braucht).



  • seldon schrieb:

    Letztlich sind die Vorbehalte gegen dynamic_cast aber nur in einem objektorientierten Kontext zu verstehen. Die Notwendigkeit, den genauen Typ eines Objektes zur Laufzeit herausfinden zu müssen, widerspricht dem objektorientierten Gedanken der Kapselung völlig - der Client-Code soll die Implementationsdetails der Objekte, mit denen er arbeitet, nicht kennen müssen, sondern lediglich ihre Schnittstelle.

    Exakt. Ein dynamic_cast tritt eigentlich praktisch immer als Symptom eines Widerspruchs in deiner Abstraktion auf. Denn überleg mal: dynamic_cast (oder auch typeid, sei es nun selbstgebaut oder nicht) bedeutet doch, dass du Objekte eines konkreten Typs an einer Stelle, an der deine Abstraktion eigentlich vorsieht, dass alle Objekte unabhängig ihres konkreten Typs gleich behandelt werden sollen, anders behandeln willst. Sowas sollte schwer zu denken geben...



  • Wenn jemand einen Compiler oder etwas vergleichbares ohne explizite Typabfragen kennt, immer her damit. Die meisten sind ja im prozeduralen oder funktionalen Stil und haben diesbezüglich natürlich wenig Hemmungen, aber selbst die wenigen objektorientierten Compiler, die ich gesehen habe, machen sowas.



  • Der Epoch-Compiler kommt ohne aus. Also in der Compilerlogik selbst; den Code, den der Compiler übersetzt, muss der Compiler natürlich ganz anders behandeln.



  • seldon schrieb:

    Der Epoch-Compiler kommt ohne aus.

    Läßt der auch einen Optimierer über den Baum laufen?



  • Exakt. Ein dynamic_cast tritt eigentlich praktisch immer als Symptom eines Widerspruchs in deiner Abstraktion auf. Denn überleg mal: dynamic_cast (oder auch typeid, sei es nun selbstgebaut oder nicht) bedeutet doch, dass du Objekte eines konkreten Typs an einer Stelle, an der deine Abstraktion eigentlich vorsieht, dass alle Objekte unabhängig ihres konkreten Typs gleich behandelt werden sollen, anders behandeln willst. Sowas sollte schwer zu denken geben...

    Mal ne Verständnisfrage, wenn ich code habe der zu 95% die Vererbungshirarchie "normal" verwendet (Beispiel: Objekte in einem Spiel, die nach unten hin mehr ausdifferenziert werden, von "du bist in der welt" zu "du wirst in der welt animiert" zu "du bist für die physik relevant" zu "du kannst dich selbst bewegen")
    Jetzt will ich alle Objekte (z.B. zu test/debugging zwecken) eines bestimmten subtyps entfernen. Wie würdet ihr sowas ohne dynamic_cast/typeid - oder ersatz - machen? Ich seh da keine sinnvolle andere Möglichkeit. Ideen? (Nicht das ich den Fall in aktuellem code hätte, bin nur neugierig)

    Ein anderer Fall, Vererbung wie oben, ich würde ein spezifisches Objekt aus dem container holen (über ne map) und obwohl ich eigentlich "weiß" was für ein subtyp das ist - ich kenn das objekt das ich hole - will ich damit der code robuster ist das nochmal prüfen. (z.B. ich will was an der animation ändern, und will nur sichergehen, dass es stimmt - oder "schlimmer", diese funktion kann über lua aufgerufen werden, und um den lua-nutzer nicht zu erlauben das programm zu crashen müsst ich prüfen)

    De facto is das einzige dynamic_cast das ich je benutzt hab absolut sinnlos gewesen, aber die paar Fragen hab ich irgendwie doch.



  • kleiner Troll schrieb:

    Jetzt will ich alle Objekte (z.B. zu test/debugging zwecken) eines bestimmten subtyps entfernen. Wie würdet ihr sowas ohne dynamic_cast/typeid - oder ersatz - machen? Ich seh da keine sinnvolle andere Möglichkeit. Ideen? (Nicht das ich den Fall in aktuellem code hätte, bin nur neugierig)

    Ich würde dafür sorgen, dass diese Objekte gar nicht erst "eingefügt" werden...

    kleiner Troll schrieb:

    Ein anderer Fall, Vererbung wie oben, ich würde ein spezifisches Objekt aus dem container holen (über ne map) und obwohl ich eigentlich "weiß" was für ein subtyp das ist - ich kenn das objekt das ich hole - will ich damit der code robuster ist das nochmal prüfen. (z.B. ich will was an der animation ändern, und will nur sichergehen, dass es stimmt - oder "schlimmer", diese funktion kann über lua aufgerufen werden, und um den lua-nutzer nicht zu erlauben das programm zu crashen müsst ich prüfen)

    Das versteh ich nicht so ganz.



  • Das erste ist keine option - ich will die Objekte ja drinnen haben, bis ich z.B. console öffne und "Remove Enemies" oder so eingebe. Gar nicht erst einfügen heißt ja, ich habe nie "Enemies" in meiner Spielwelt - das halte ich für schwachsinn.

    Letzeres ein konkretes Beispiel:

    SetAI soll eine lua-funktion sein die den Namen eines Objekts nennt, und die Kampf-KI dafür einstellt

    SetAI("Oberbösewicht", "Aggressive")
    

    Bedingung ist natürlich, dass das benannte Objekt überhaupt eine Kampf-KI hat.

    //map<string, Object*> objectMap;
    CombatObject* co = dynamic_cast<CombatObject*>(objectMap["Oberbösewicht"]);
    if(co) {
    SetAI("Aggressive");
    }
    else {
    lua_error("Dummer nutzer, 'Oberbösewicht" kann doch gar nicht kämpfen!")
    }
    


  • kleiner Troll schrieb:

    Gar nicht erst einfügen heißt ja, ich habe nie "Enemies" in meiner Spielwelt - das halte ich für schwachsinn.

    Nein, das impliziert nicht, dass du keine Gegner haben kannst. Aber du sollst nicht alles in einen einzelnen Container werfen, wenn du nachher wieder Fallunterscheidungen benötigst. Warum keinen separaten Container für Gegner?

    kleiner Troll schrieb:

    SetAI soll eine lua-funktion sein die den Namen eines Objekts nennt, und die Kampf-KI dafür einstellt

    Und warum ist dann SetAI() keine virtuelle Methode der Object -Klasse? Du könntest z.B. als Default-Verhalten nichts tun und einen Fehler zurückgeben, und für CombatObject -Objekte eben die KI setzen.



  • Nexus schrieb:

    kleiner Troll schrieb:

    Gar nicht erst einfügen heißt ja, ich habe nie "Enemies" in meiner Spielwelt - das halte ich für schwachsinn.

    Nein, das impliziert nicht, dass du keine Gegner haben kannst. Aber du sollst nicht alles in einen einzelnen Container werfen, wenn du nachher wieder Fallunterscheidungen benötigst. Warum keinen separaten Container für Gegner?

    Das würde die Hauptschleife zerhacken. Nein.

    Nexus schrieb:

    kleiner Troll schrieb:

    SetAI soll eine lua-funktion sein die den Namen eines Objekts nennt, und die Kampf-KI dafür einstellt

    Und warum ist dann SetAI() keine virtuelle Methode der Object -Klasse? Du könntest z.B. als Default-Verhalten nichts tun und einen Fehler zurückgeben, und für CombatObject -Objekte eben die KI setzen.

    Ja, so in der Richtung.



  • Naja, wie wärs mit einer Methode isCombatant()? Ich unterstelle mal, dass du bei einem Spiel schon beim Design der Basisklasse vorhersehen kannst, dass es kämpfende und nicht kämpfende Figuren gibt.

    Im Allgemeinen ist das ein Problem, das als Expression Problem bekannt ist. OOP macht es einfach, neue Subtypen hinzufügen, und schwer, neue Operationen hinzuzufügen. Wenn man explizite Fallunterscheidungen für die Subtypen hat, ist es genau umgekehrt. Man kann auch das Visitor-Pattern verwenden, um das Problem anders zu verlagern, damit ist es dann wieder leicht, neue Operationen einzuführen, und schwer, neue Subtypen hinzuzfügen. Wenn man die Fixkosten des Visitor-Patterns tragen will.

    Ich habe in der Regel mit Problemen zu tun, bei denen eher neue Funktionen als neue Subtypen hinzukommen. Deshalb hab ich das mit den Compilern erwähnt. Ich bin auch aktiv auf der Suche nach Compilern, die in dem Punkt elegante Lösungen bieten. Epoch hab ich mir noch nicht angesehen, aber das werde ich garantiert noch tun.


Anmelden zum Antworten