Frage an alle die denken, dynamisches casting ist ein Zeichen von schlechtem Softwaredesign



  • 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 typeinfo und einem static_cast einer Prüfung durch dynamic_cast vorziehen.



  • 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



  • volkard schrieb:

    loki ist doch gar nicht boost.

    Beide haben aber gemeinsam, dass sie viel auf Templates und besonders Metaprogrammierung setzen. Manchmal habe ich bei dir das Gefühl, du hättest eine generelle Abneigung gegen diese Art von Bibliothek. 😉

    volkard schrieb:

    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.

    Im Buch Modern C++ Design hat Andrei Alexandrescu eine Klasse BasicFastDispatcher vorgestellt, die von beiden zu dispatchenden Objekten einen Index abfragt und an der entsprechenden Stelle in einer Lookup-Table die richtige Funktion aufruft. Also eine Art zweidimensionale VTable. Wie ich jetzt gesehen habe, ist die Klasse aus irgendeinem Grund nicht in der Loki-Bibliothek. Merkwürdig...

    volkard schrieb:

    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.

    Alexandrescu sagt in seinem Buch, BasicFastDispatcher habe einen Overhead von zwei virtuellen Funktionsaufrufen, einem Array-Zugriff und einem Funktionszeiger-Aufruf. Also leicht mehr als beim Visitor, was ich für vertretbar halte.



  • Im Buch Modern C++ Design hat Andrei Alexandrescu eine Klasse BasicFastDispatcher vorgestellt, die von beiden zu dispatchenden Objekten einen Index abfragt und an der entsprechenden Stelle in einer Lookup-Table die richtige Funktion aufruft. Also eine Art zweidimensionale VTable. Wie ich jetzt gesehen habe, ist die Klasse aus irgendeinem Grund nicht in der Loki-Bibliothek. Merkwürdig...[/quote]
    Merkwürdig.
    Oh, das habe ich um 17 Sep 2010 11:50 ja auch skizziert. Zufälle gibt's...

    Nexus schrieb:

    volkard schrieb:

    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.

    Alexandrescu sagt in seinem Buch, BasicFastDispatcher habe einen Overhead von zwei virtuellen Funktionsaufrufen, einem Array-Zugriff und einem Funktionszeiger-Aufruf. Also leicht mehr als beim Visitor, was ich für vertretbar halte.

    Ja, scheint ganz schnell. Aber der war ja nicht drin.
    Manchmal sind Aufrufe über Methodenzeiger sehr langsam in Vergleich zu einfachen virtuellen Funktionsaufrufen. Ein O(1)-Dispatcher könnte auch eine sehr unangemessene Universal-Hashtable drin haben und eine Division durch einen oberen Primzahlenzwilling opfern. Naja, ich möchte bei halt genau wissen, was unter der Motorhaube los ist.


Anmelden zum Antworten