Frage an alle die denken, dynamisches casting ist ein Zeichen von schlechtem Softwaredesign
-
volkard schrieb:
Wie ich das verstanden habe:
Es gibt eine schlanke XML-Bibliothek, wo diese Funktionalität fehlt.Das Programm Lasttest benutzt die Bibliothek und freut sich über das eingesparte Kilobyte. Lasttest soll so schnell wie möglich sein. Lasttest braucht die Funktionalität nicht.
Das Programm XML-Editor braucht die Funktionalität und nimmt einfach dynamic_cast. Der XML-Editor kann beliebig inperformant sein.
So ähnlich kannst Du es formulieren. "beliebig inperformant" ist allerdings etwas übertrieben.

volkard schrieb:
Es bleibt, daß ich auch hier dynamic_cast für schlecht halte und lieber die Bibliothek aufmotzen würde. Falls sie aus dem eigenen Hause stammt oder ich sonst einfachen Einfluß darauf habe.
Der XML-Standard ist nicht sehr volatil. Und gerade weil alles aus einer Hand stammt, kann ich sehr gut auf Veränderungen reagieren. Aber eines ist mir natürlich bewusst, wenn ich die Bibliothek veröffentlichen würde, wären natürlich andere Anforderungen an diese zu stellen und die Effizienz müsste darunter "leiden", da in diesem Falle natürlich die Abstraktion auf ein wesentlich höheres Niveau steigen muß als es jetzt ist.
volkard schrieb:
Und es bleibt
die Aussage, dynamisches casten sei ein Zeichen schlechten Softwaredesigns, ist völliger Humbug.
kann man nicht so stehen lassen.
Ich seh ein, da habe wohl ich übertrieben.
mfg Martin
-
Hei Leut, mal wieder zurück zum Thema! :p
Also ich habe das Ganze mittlerweile mit dem double dispatcher Pattern implementiert und funktioniert auch ganz dolle. Nun kommt aber gleich wieder ein Problem und zwar muss ich die BoundingVolumeHierarchie rendern (zu Debugginzwecken). Der Renderer muss also IMHO die BoundingVolumes kennen, umgekehrt aber nicht. Nun ist es so, dass bspw. eine BoundingSphere vollständig anders gerendert wird, als eine kDOP oder eine OOB. Ausserdem haben diese natürlich auch ganz andere Membervariablen. Für die Physikengine ist es gut gelöst, die kommuniziert nämlich ausschliesslich im der Abstrakten Klasse BoundingVolume und muss nichts über die interne Geometry wissen. Die will nur wissen, hat eine Kollision stattgefunden und falls ja an welcher Stelle im kartesichen Raum. Der Renderer hingegen muss nun sehr genau wissen, wie die Geometry des entsprechenden Proxykörpers aufgebaut ist. Da aber eine BVH ein Baum von BoundingVolumes ist frage ich mich, wie ich dies nun ohne dynamic_cast lösen kann?
-
Warum nicht? Die virtuelle Funktion im Objekt weiß doch, welches BoundingVolume passend ist.
-
Ja aber nicht welches Mesh für das Rendern verwendet werden soll, da die BoundingVolume nichts mit Grafik am Hut hat. Ausserdem werden OBB's als Indexed line list, kDops als indexed Trianglelists und BoundingSpheres als vorgeladenes Mesh gerendert. Alles Informationen, über welche die BoundingVolume nicht verfügt, der Renderer hingegen schon. Dafür weiss der Renderer ohne dynamic_cast nicht, wie die Paramater der jeweiligen geometrischen Form aussehen.
So nun bin ich gespannt!

-
Wie siehts mit dem Visitorpattern aus?
IVolume* vol; Renderer r; vol->accept(r); // vol ruft dann die passende Methode im Renderer aufSozusagen SingleDispatching anders rum
-
Dafür muss das Volume den Renderer kennen und das ist überhaupt nicht in meinem Sinne (Package Dependency, violation of Layer contraints)! Also eine Abhängigkeit von der Mathlibrary zur Renderlibrary? Mit anderen Worten, ich kann die Mathlibrary in anderen Projekten nur dann benutzen, wenn ich ebenfalls die Renderlibrary hinzunehme? Nein Danke! :p
-
Findet ihr, dass double dispatching wirklich schönes Design ist? Ich finde, dass das nur eine ganze Menge schreibarbeit ist, vorallem, wenn man ca. 10 verschiedene Objekttypen hat.
Was macht man z.B. wenn man eine fertige 3rd Party Lib ohne Source hat, die Kollisionserkennung für ein paar Grundtypen hat und man will noch User-Definierte-Objekte zulassen? Da kann ich ja schlecht mit double dispatching arbeiten.
-
@mal ehrlich
Meine Rede!
-
mal ehrlich schrieb:
Findet ihr, dass double dispatching wirklich schönes Design ist? Ich finde, dass das nur eine ganze Menge schreibarbeit ist, vorallem, wenn man ca. 10 verschiedene Objekttypen hat.
Double Dispatching ist unschön und führt zu viel Boilerplate-Code, wenn man es intrusiv implementiert. Deshalb gibt es Leute wie Andrei Alexandrescu, die sich elegantere Möglichkeiten ausgedacht haben.

Hier ein Beispiel der Verwendung von Double-Dispatch mit der Loki-Bibliothek:
#include <Loki/MultiMethods.h> #include <iostream> struct Object { virtual ~Object() = 0 {} }; struct Asteroid : Object { }; struct SpaceShip : Object { }; void Collision(Asteroid&, Asteroid&) { std::cout << " Asteroid <-> Asteroid" << std::endl; } void Collision(Asteroid&, SpaceShip&) { std::cout << " Asteroid <-> SpaceShip" << std::endl; } void Collision(SpaceShip&, SpaceShip&) { std::cout << "SpaceShip <-> SpaceShip" << std::endl; } int main() { // Dispatch-Objekt erstellen; Object = Basisklassentyp Loki::FnDispatcher<Object> disp; // Einzelne Funktionen registrieren; sym=symmetrisch bedeutet Fn(a,b) == Fn(b,a) // LHS RHS Funktion sym? disp.Add<Asteroid, Asteroid, &Collision>(); disp.Add<Asteroid, SpaceShip, &Collision, true>(); disp.Add<SpaceShip, SpaceShip, &Collision>(); // Referenzen auf abstrakte Basisklasse erstellen Asteroid a; SpaceShip s; Object& pa = a; Object& ps = s; // Multimethoden aufrufen disp.Go(a, a); disp.Go(a, s); disp.Go(s, a); // <- Symmetrie! disp.Go(s, s); }
-
Grüße!
Also dynamic_cast an sich ist kein Zeichen für schlechtes Design. Wer das behauptet übertreibt ein wenig. Und weder Schneeweiß, noch Alexandrescu noch Meyers behaupten so etwas. Im zitierten Buch von Scott Meyers steht drin, ich habe es gerade nachgeschlagen, dass es ein Indiz für ein fehlerhaftes Design ist, wenn man sich gezwungen sieht eine dynamic_cast-Kaskade schreiben zu müssen. (Heißt, jede Menge if-else-if Tests mit dynamic_cast auf deine Objekte.)
dynamic_cast an sich ist zwar "teuer" im Sinne von "Kostet Zeit", aber wenn der Nutzen die Kosten überwiegt, bitte. (Bei einer Physik-Engine glaube ich da aber nicht so recht dran.)
Eigentlich stellt sich zuerst die Frage, wie du das Problem gelöst haben möchtest. Zur Kompilierzeit? Dann wäre vielleicht ein TMP-Ansatz, wie zum Beispiel über <tr1/type_traits>, möglich? Oder wird deine Engine keine zeitkritischen Einsätze haben, so dass eine Laufzeitlösung über RTTI, wie zum Beispiel mit <typeinfo>, in Betracht käme?
Bei nur zwei Klassen, würde ich eine Lösung mit
typeinfound einemstatic_casteiner Prüfung durchdynamic_castvorziehen.
-
Nexus schrieb:
mal ehrlich schrieb:
Findet ihr, dass double dispatching wirklich schönes Design ist? Ich finde, dass das nur eine ganze Menge schreibarbeit ist, vorallem, wenn man ca. 10 verschiedene Objekttypen hat.
Double Dispatching ist unschön und führt zu viel Boilerplate-Code, wenn man es intrusiv implementiert. Deshalb gibt es Leute wie Andrei Alexandrescu, die sich elegantere Möglichkeiten ausgedacht haben.

Hier ein Beispiel der Verwendung von Double-Dispatch mit der Loki-Bibliothek:
#include <Loki/MultiMethods.h> #include <iostream> struct Object { virtual ~Object() = 0 {} }; struct Asteroid : Object { }; struct SpaceShip : Object { }; void Collision(Asteroid&, Asteroid&) { std::cout << " Asteroid <-> Asteroid" << std::endl; } void Collision(Asteroid&, SpaceShip&) { std::cout << " Asteroid <-> SpaceShip" << std::endl; } void Collision(SpaceShip&, SpaceShip&) { std::cout << "SpaceShip <-> SpaceShip" << std::endl; } int main() { // Dispatch-Objekt erstellen; Object = Basisklassentyp Loki::FnDispatcher<Object> disp; // Einzelne Funktionen registrieren; sym=symmetrisch bedeutet Fn(a,b) == Fn(b,a) // LHS RHS Funktion sym? disp.Add<Asteroid, Asteroid, &Collision>(); disp.Add<Asteroid, SpaceShip, &Collision, true>(); disp.Add<SpaceShip, SpaceShip, &Collision>(); // Referenzen auf abstrakte Basisklasse erstellen Asteroid a; SpaceShip s; Object& pa = a; Object& ps = s; // Multimethoden aufrufen disp.Go(a, a); disp.Go(a, s); disp.Go(s, a); // <- Symmetrie! disp.Go(s, s); }Dann schau mal den Code dazu an:
http://loki-lib.sourceforge.net/html/a00658.html
Da wird typeid, callbackMaps, DynamicCaster usw. verwendet. Im Prinzip so ähnlich, wie ich es auch gemacht hätte, wenn ich was erweiterbares brauchen würde, wenn ich auch nicht so viel mit Templates gemacht hätte. Mit double dispatching hat das eigentlich nix mehr zu tun.
-
mal ehrlich schrieb:
Mit double dispatching hat das eigentlich nix mehr zu tun.
Doch. Nur musst du dann den Begriff etwas weiter fassen als nur die eine konkrete Implementierung über gegenseitige virtuelle Methodenaufrufe im Visitor-Stil. Die ist nämlich wie schon angetönt für viele Fälle zu unflexibel und schlecht erweiterbar.
Ich verstehe unter Double-Dispatching die Möglichkeit, aufgrund zweier dynamischer Typen zur Laufzeit die richtige Funktion aufzurufen. Die konkrete Implementierung ist dabei hintergründig, im Zentrum steht die Anwendung. Es gibt Sprachen, die haben dieses Feature bereits eingebaut.
Immer noch kein Grund, einen Full-Quote zu machen.

-
Ich hab seinen Code jetzt nur überflogen, aber eigentlich hat es sowas gemacht.
map< pair<ClassID1, ClassID2>, callbackFunc > go(class1, class2) { callbackFunc = map.find(class1.ID, class2.ID) callbackFunc(class1, class2) }Das wird doch keiner als double dispatch bezeichnen.
-
mal ehrlich schrieb:
Das wird doch keiner als double dispatch bezeichnen.
Wie schon erwähnt: Warum interessiert dich die Implementierung? Für mich ist Double-Dispatch ein Konzept und keine konkrete Realisierung davon. Du klammerst dich an den einen speziellen Fall fest.
Ishildur geht es darum, abhängig von zwei dynamischen Typen eine jeweils andere Funktion aufzurufen. Also im Prinzip Funktionsüberladung, aber eben nicht mit statischen Typen, sondern zur Laufzeit. Und dafür gibt es mehrere Möglichkeiten, wobei ich Alexancrescus Ansatz sehr elegant finde, hauptsächlich aus den folgenden Gründen:
- Kein intrusiver Code: Man kann Klassen unverändert lassen. Die Verwendung symmetrischer freier Funktionen anstelle virtueller Methoden hat ebenfalls Vorteile.
- Erweiterbarkeit: Man kann ohne viel Aufwand neue Multimethoden hinzufügen, die automatisch berücksichtigt werden.
-
mal ehrlich mal ehrlich du bist zu ehrlich
-
Wir verwenden Double-Dispatching um dynamic_cast zu umgehen, da Double-Dispatching aber so viel Tipparbeit ist, nehmen wir den Loki-Function-Dispatcher der wieder dynamic_cast verwendet, aber es gut versteckt.

-
Zusammenfassung schrieb:
Wir verwenden Double-Dispatching um dynamic_cast zu umgehen, da Double-Dispatching aber so viel Tipparbeit ist, nehmen wir den Loki-Function-Dispatcher der wieder dynamic_cast verwendet, aber es gut versteckt.

Nein.
Ich verwende den visitor-pattern-ähnlichen Ping-Pong-Trick, um double Dispatching zu implementieren. Dabei ist es nicht so, daß ich dynamic_cast umgehen würde, weil dynamic_cast gar nicht zum Problem paßt und nie in Gefahr war, benutzt zu werden. Ein paar Zeilen intrusiven Codes stören mich nicht und er ist mir auch nicht zu wenig erweiterbar, also muß ich mir loki mit nur logarithmischer Laufzeit nicht antun, um double Dispatching verwenden zu können.
-
volkard schrieb:
Ein paar Zeilen intrusiven Codes stören mich nicht und er ist mir auch nicht zu wenig erweiterbar, also muß ich mir loki mit nur logarithmischer Laufzeit nicht antun, um double Dispatching verwenden zu können.
Wärst du nicht so voreingenommen wie gegenüber Boost, hättest du dir Loki etwas genauer angeschaut und gemerkt, dass es unterschiedliche Implementierungen mit unterschiedlichem Laufzeitverhalten gibt. Darunter ein Dispatcher mit O(1), der aber immer noch relativ gut erweitert werden kann. Klar ist der Visitor-Ansatz manchmal einfach die beste Lösung, aber gerade wenn die Geschwindigkeit nicht kritisch ist, kann sich ein Blick auf Alternativen lohnen.
-
Nexus schrieb:
volkard schrieb:
Ein paar Zeilen intrusiven Codes stören mich nicht und er ist mir auch nicht zu wenig erweiterbar, also muß ich mir loki mit nur logarithmischer Laufzeit nicht antun, um double Dispatching verwenden zu können.
Wärst du nicht so voreingenommen wie gegenüber Boost, hättest du dir Loki etwas genauer angeschaut und gemerkt, dass es unterschiedliche Implementierungen mit unterschiedlichem Laufzeitverhalten gibt. Darunter ein Dispatcher mit O(1), der aber immer noch relativ gut erweitert werden kann. Klar ist der Visitor-Ansatz manchmal einfach die beste Lösung, aber gerade wenn die Geschwindigkeit nicht kritisch ist, kann sich ein Blick auf Alternativen lohnen.
loki ist doch gar nicht boost.
(Ich und voreingenommen gegen boost? Kann man wohl so sagen. Eigentlich voreingenommen gegen die Richtung, sich für jedes Problem eine eigenes Sprachumfeld zu schaffen, das ist bei boost im Allgemeinen recht ausgeprägt.)
hab sogar in den code reingerochen. download der loki-0.1.7.zip um 10:51.
Ich sah da den class template StaticDispatcher mit dem dynamic_cast drin und typelists, so von angucken her hätte ich den für O(n) gehalten, und angenommen, er hangelt sich rekursiv den typelists entlang.
Und den class template BasicDispatcher mit O(log(n)), steht schon drüber.
Und selbst bei O(1) würde ich erstmal kritisch prüfen, ob das eine eher schnelle oder eher langsame 1 ist. Nur zwei virtuelle Funktionsaufrufe nebst Parameter-Forwarding halte ich für eine eher schnelle 1.
-
volkard schrieb:
hab sogar in den code reingerochen. download der loki-0.1.7.zip um 10:51.
mal ehrlich schrieb:
Dann schau mal den Code dazu an:
http://loki-lib.sourceforge.net/html/a00658.html