störendes dynamic_cast: wie krieg ich das hier weg?



  • otze schrieb:

    somit wäre ein Aufruf von getChild in einem nicht-Frame Objekt immer ein Fehlerfall.

    lies dir composite nochmal durch.

    Es passt schon, es _ist_ ein fehlerfall.

    Die Idee dahinter: du musst den fehlerfall nicht explizit handlen. sondern das system uebernimmt das automatisch.

    Zum anderen würde ich damit ein "internes" Problem öffentlich machen. Bislang ist dieses Problem von aussen nicht erkennbar, da es sich auf eine Zeile einer Funktion beschränkt. Vielleicht seh ich das ein wenig zu drastisch, aber ich weis nicht, ob ein dynamic_cast schlimmer ist, als eine methode in einer Klasse, die sich zudem nur darauf beschränken würde, eine exception zu werfen 😉 - und damit nur verwirrung stiften würde.

    nein, keine exception.
    kein fehler.

    es liefert dir einfach nur eine liste von 0 childs.



  • Ja, aber das _ist_ ein schwerwiegender Fehler. Denn der bedeuted, dass entweder die GUI falsch zusammengebastelt wurde, oder die funktionen die die eingaben der GUI verarbeiten falsch progammiert sind.

    Und natürlich müsste ich das immernoch Handlen. Denn was bedeutet es, wenn ein getChild kein Ergebnis liefert? Es bedeuted, dass ich mit einem gesuchten Objekt keinen Kontakt aufnehmen kann, weil es entweder nicht da ist, oder nicht richtig benannt wurde, und ich denke schon, dass dies Exception würdig ist. Und ob ich diese dann in der verarbeitenden Funktion werfe, oder direkt im getChild mechanismus ist dann jacke wie Hose(obwohl es wesentlich mehr schreibarbeit wäre, dies in jeder verarbeitenden funktion zu tun).



  • Du musst das System natuerlich anpassen. Du kannst nicht dein jetztiges system 1:1 uebernehmen und keine dynamic_casts haben.

    Composite ist ein Beispiel wie du es ohne dynamic_cast machen kannst. Schau dir das Pattern an. Viele GUI Toolkits basieren darauf.

    Schau dir das Pattern mal genau an und erklaer mir warum du es bei dir nicht implementieren kannst/willst.



  • Erstmal vorweg, das Pattern selbst ist für mich nichts neues, nur unter diesem namen kannte ich das bisher noch nicht. Bisher nannte ich es immer: "So hab ich das mal bei nie fertig gewordenem projekt X gemacht".

    Natürlich basieren einige GUI Toolkits darauf, und es ist sicher keine schlechte idee. Aber ich möchte es so nicht implementieren. Natürlich hab ich dafür auch Gründe vorzuführen ;).

    Ich habe die ganze zeit über immer versucht, jedes noch so kleine Teilinterface möglichst einfach und sinnvoll zu gestalten. Am Anfang jeder Überlegung stand immer die Frage, ob die Methode an der Stelle sinnvoll ist. In einer Klasse Control ist dies sicher nicht der Fall. Ein Controlobjekt hat sicherlich niemals Kinder, und somit hat es keinen Sinn, dass es eine Methode getChild hat.

    Das nächste Problem ist, dass ich - wie schonmal gesagt - ein internes Problem nach aussen Trage. Und "aussen" ist hierbei eine Stelle, die normal nichts davon mitkriegen würde. Nach der erstellung der GUI sollte am ende immer ein zeiger auf ein Frame Objekt übrig bleiben, sodass die getChild funktion von da aus sehr gut aufgerufen werden kann. Es besteht garkeine notwendigkeit, dass diese von einem Controlzeiger aufgerufen wird.

    Der für mich wichtigste Grund ist aber, dass es auf mich wie mit Kanonen auf Spatzen schießen aussieht. Der Grund weshalb ich den dynamic_cast brauche ist, dass ich nicht sicher sein kann, dass es in dem Frame wirklich irgendwo das Kind "Kind.Enkel.Urenkel" gibt. Ansonsten würde ein einfacher static_cast reichen, da der fakt, dass nach "Kind" und "Enkel" noch "Urenkel" folgt, ausschließen sollte, dass diese Namen nicht zu Frames gehören - natürlich nur, wenn der Programmierer keinen Fehler macht.



  • Tja, das Composite Pattern ist genau dafuer da in so einer Situation den dynamic cast zu entfernen...

    Jedes Design hat vor und nachteile.

    bei composite hast du halt den vorteil dass dein code sauberer und leichter erweiterbar wird und du dafuer ein etwas groesseres interface hast. wenn dir das interface zu wichtig ist, dann wirst du downcasts brauchen.



  • Frame* frame = control->IsFrame() ? static_cast<Frame*>(control) : 0;

    Dem User keine Möglichkeit zu bieten die "is frame" Frage ohne dynamic_cast zu beantworten halte ich z.B. für noch viel unsauberer als einfach "getChild" gleich in Control anzubieten.

    Andere Möglichkeit: das Frame kann sich beim "reintun" von dem Control in das Frame leicht merken ob ein Control denn ein Frame ist oder nicht - dann brauchst du bloss dort einen dynamic_cast. Wobei ich wirklich nichts Schlimmes an der Verwendung von dynamic_cast finden kann. Ok, die MSVC Implementierung von dynamic_cast is viel langsamer als sie sein müsste, aber das sollte in dem Fall vollkommen egal sein, würde mich wundern wenn man den Unterschied spürt.



  • Ja, das Problem ergibt sich oft: Breite, polymorphe schnittstelle (mit der Gefahr daß es viele sinnlose methoden gibt) oder schlankes interface, das wirklich nur erlaubt, was Sinn macht (aber dafür viele dynamic_casts erfordert).

    Ich habe bis heute keine regel gefunden, wie man entscheiden kann, welche methode man bevorzugt. Gefühlssache.

    Aber mal eine andere Frage: Wie lange dauert denn ein solches dynamic_cast bei verschiedenen Compilern? Warum ist es bei VC++ langsamer? Wieviel?



  • Bei verschiedenen Compilern? Weiss ich nicht. Ich weiss bloss dass MSVC strcmp() aufruft - und je tiefer er suchen muss, umso öfter. Und strcmp() ist halt langsamer als Hashcodes oder gleich UIDs zu vergleichen.

    Ob das andere Compiler anders machen weiss ich nicht, kann leicht sein dass die meisten Compiler hersteller dynamic_cast nicht als wichtig genug sehen um das ordentlich zu optimieren.

    Der Vergleich von type_info Instanzen ist übrigens auch über strcmp() implementiert, aber da wird wenigstens immer bloss ein String verglichen.



  • Plotzenhotz schrieb:

    Bei verschiedenen Compilern? Weiss ich nicht. Ich weiss bloss dass MSVC strcmp() aufruft - und je tiefer er suchen muss, umso öfter. Und strcmp() ist halt langsamer als Hashcodes oder gleich UIDs zu vergleichen.

    Ob das andere Compiler anders machen weiss ich nicht, kann leicht sein dass die meisten Compiler hersteller dynamic_cast nicht als wichtig genug sehen um das ordentlich zu optimieren.

    Der Vergleich von type_info Instanzen ist übrigens auch über strcmp() implementiert, aber da wird wenigstens immer bloss ein String verglichen.

    Das ist wohl eine Unterschied, ja. Aber per String... gibt es da keine bessere Methode?!

    Naja. Aber sag mal, du hast offenbar einiges an Erfahrung. Bezüglich dem was ich im vorletzten post schrieb... hast du für dich Regeln gefunden, nach denen du breites vs. schlankes interface abwägst?



  • otze schrieb:

    Ja, aber das _ist_ ein schwerwiegender Fehler. Denn der bedeuted, dass entweder die GUI falsch zusammengebastelt wurde, oder die funktionen die die eingaben der GUI verarbeiten falsch progammiert sind.

    Und natürlich müsste ich das immernoch Handlen. Denn was bedeutet es, wenn ein getChild kein Ergebnis liefert? Es bedeuted, dass ich mit einem gesuchten Objekt keinen Kontakt aufnehmen kann, weil es entweder nicht da ist, oder nicht richtig benannt wurde, und ich denke schon, dass dies Exception würdig ist. Und ob ich diese dann in der verarbeitenden Funktion werfe, oder direkt im getChild mechanismus ist dann jacke wie Hose(obwohl es wesentlich mehr schreibarbeit wäre, dies in jeder verarbeitenden funktion zu tun).

    Control::getChilds() liefert doch ein Ergebniss: Nämlich eine leere Liste. Eine leere Liste ist immernoch eine Liste.

    for ( Child child : getChilds() )
    {
        child->machewas();
    }
    

    Der obige Code würde keinen Fehler erzeugen, denn seit wann ist es ein Fehler wenn ein Kind keine Kinder hat?
    Ein Frame kann genauso keine Child haben, ist halt leer das Fenster, es würde sich genauso verhalten wie ein Child.

    Ausserdem stell dir vor du hast eine GUI mit 100 Elementen und dann jedesmal ein dynamic_cast?



  • DEvent schrieb:

    Der obige Code würde keinen Fehler erzeugen, denn seit wann ist es ein Fehler wenn ein Kind keine Kinder hat?
    Ein Frame kann genauso keine Child haben, ist halt leer das Fenster, es würde sich genauso verhalten wie ein Child.

    Wenn ich getChild() aufrufen würde, und nix wiederbekomme, wäre das kein Fehler, was aber, wenn ich getChild("Kindname") aufrufe?
    Wenn ich nach einem bestimmten Kind suche zb nach dem Button "Agree" dann ist es scheisse, wenn der nicht da ist, weil ich dem dann nicht meinen Callback übergeben kann. Ohne callback weis ich aber nicht, wann auf den Button geklickt wurde, und wenn ich das nicht weis, wird der benutzer sauer, weil er auf einen nicht existierenden Button klicken will, und dann nichts passiert ;).

    So, und was daran ist nun kein Fehler?

    Ausserdem stell dir vor du hast eine GUI mit 100 Elementen und dann jedesmal ein dynamic_cast?

    Für die Funktionalität um die es hier geht, braucht man keinen dynamic_cast. Den brauch ich nur intern.

    Problematisch wird es erst, wenn er so ein Control in ein Editfeld umwandeln will. Aber da sollte ein static_cast reichen.
    Ansich ist das aber ein generelles Problem. Entweder ich sag: Ok, ich pack die methode getString in die Control Klasse und akzeptier, dass man nun auch nen String von nem Button oder einem Bild kriegen kann, oder ich akzeptiere casts.



  • otze schrieb:

    Wenn ich getChild() aufrufen würde, und nix wiederbekomme, wäre das kein Fehler, was aber, wenn ich getChild("Kindname") aufrufe?
    Wenn ich nach einem bestimmten Kind suche zb nach dem Button "Agree" dann ist es scheisse, wenn der nicht da ist, weil ich dem dann nicht meinen Callback übergeben kann.

    das problem hast du aber _immer_ wenn du ein getChild("name") anbietest. Unabhaengig davon wie du dein system jetzt implementierst.

    So, und was daran ist nun kein Fehler?

    Was machst du wenn das Frame den button nicht kennt?

    selbes problem. ob du getChild nun in Component oder Frame packst - das Problem besteht so oder so.

    Für die Funktionalität um die es hier geht, braucht man keinen dynamic_cast. Den brauch ich nur intern.

    dynamic_cast ist dynamic_cast - wo du ihn machst ist egal. er ist da.

    Ansich ist das aber ein generelles Problem. Entweder ich sag: Ok, ich pack die methode getString in die Control Klasse und akzeptier, dass man nun auch nen String von nem Button oder einem Bild kriegen kann, oder ich akzeptiere casts.

    ueberleg dir mal warum so ziemlich jedes GUI Framework Composite verwendet.

    Weil es einfach praktisch ist und es eine menge fehlersuche und vorallem code spart.

    soviele casts wie du willst (stell dir mal vor in einem jahr hast du etwa das 10 fache an Komponenten) wird das auf dauer sehr muehselig.



  • Naja. Aber sag mal, du hast offenbar einiges an Erfahrung. Bezüglich dem was ich im vorletzten post schrieb... hast du für dich Regeln gefunden, nach denen du breites vs. schlankes interface abwägst?

    Naja, so allgemein tu' ich mir schwer dazu viel zu sagen. Aber...

    😉 Ich meide GUI Code wie die Pest (Beulenpest, die schwarze), u.A. weil ich bis jetzt noch keine ordentliche GUI Lib gesehen hab. Das Teil von Adobe könnte was können, is mir dann aber wieder viel zu heftig (eigene VM bloss für die GUI, und das ist erst der Anfang).

    😉 Ich meide wenn möglich Polymorphismus in meinen *Designs* zu haben, wenn dann als Implementierungs-Detail, aber das wars dann auch schon. Wenn möglich wie gesagt 🙂 Geht bloss leider nicht immer ganz einfach, und bei so Sachen wie nem Szenengraph ist es eigentlich doof was anderes zu machen. Reine "Interfaces" sind eine andere Sache, aber wenns geht bitte auch ohne Vererbung. Natürlich braucht man dann öfter mal Adapter, z.B. wenn man zwei generische Interfaces "RandomAccessStorage" und "InputStream" komplett getrennt voneinander definiert dann braucht man einen "InputStream_From_RandomAccessStorage_Adapter" (und ja, ich verwende so lange Klassennamen :)) der aus dem RandomAccessStorage einen InputStream macht. Allerdings kann man da wieder etwas Code sparen indem man ein Template als Adapter verwendet welches bloss voraussetzt dass der Typ mit dem man es instanziert Funktionen mit gewissen Namen hat die gewisse Dinge erledigen (also quasi ein "Concept" erfüllt). Für Fälle wo das nicht zutrifft (weil die Funktionen vielleicht anders heissen oder nicht 1:1 kompatible Semantik haben) kann man dann leicht das Template spezialisieren wo man seinen Glue-Code reinschreibt und fertig.

    😉 Eine Sprite-LIB die ich mal baute hatte das auch, also Sprite und SpriteContainer, wobei SpriteContainer selbst ein Sprite war. Da hatte ich intern auch nen dynamic_cast (weil das nackte "Sprite" eben kein Container sondern eindeutig ein Leaf des Szenengraphen war), bloss hab ich den auf die "Insert" Funktion des SpriteContainer beschränkt (ich hatte sowieso schon wesentlich mehr State zu einem Sprite als bloss den Pointer auf selbiges, da war der zusätzliche Pointer auf das SpriteContainer Interface auch schon egal). Somit in den Such- und Szenengraph-Durchklaub-Funktionen kein dynamic_cast mehr sondern nur Test ob der Pointer null ist. Im Nachhinein bin ich mir aber nichtmehr sicher ob es nicht besser gewesen wäre gleich nur eine Klasse zu haben.

    😉 dynamic_cast ist wie goto: grundsätzlich nicht böse, man muss bloss wissen was man tut 😃 (und ja, ich verwende goto, z.B. als "double-break", aber bloss ganz selten *pfeiff*)

    Nochmal zu dem spezifischen Fall Szenengraph zurück: wenn dein einziger (oder schwerwiegendster) Grund der ist dass du in GetChild() weniger Fehlerfälle haben willst, dann ist es wurst, weil du wie Shade Of Mine schon schrieb sowieso immer den Fall handlen muss dass jmd. ein Child haben will welches es in dem Container aber nicht gibt. Anders sieht es mit den "Insert()" und "Remove()" Funktionen aus, aber die musst du ja nicht unbedingt in Control implementieren. Genausowenig wie die GetChild() Funktion in Control public sein muss - wozu gibts "friend"? Und die GetChild() Funktion eines Composite-Controls muss ja auch nicht unbedingt die einzelnen Elemente aus denen das zusammengesetzt ist zugänglich machen.

    Grundsätzlich solltest du aber beim Interface anfangen, also quasi aus Sicht des Programmierers der deine LIB verwendet. Erstmal ohne grossartig zu überlegen wie du das dann im Detail implementierst. Wenn du dann mal ein gutes Interface hast kannst du dir Gedanken machen ob du dann in der Implementierung nen dynamic_cast oder eine "IsFrame" Funktion oder eine private "GetChild" Funktion verwendest. Vergiss dabei aber z.B. nicht dass User auch selbst ihre eigenen Composite-Controls implementieren werden wollen -- du solltest also auch diverse Sachen die du z.B. selbst brauchst um gewisse Controls zu implementieren dem User nicht völlig verschliessen, der ärgert sich sonst nämlich.

    Und klapper ruhig gewisse Foren zu gewissen bestehenden GUI LIBs ab und frag die Leute dort was gut ist und was schlecht, was sie häufig einsetzen, was nie, was fehlt etc.

    Ja nu, viel geschrieben, wenig gesagt. Vielleicht ein andermal mehr, jetzt muss ich heia, auch Räuber mit geschüttelten Namen müssen arbeiten 😞


Anmelden zum Antworten