User soll Code einfügen. Ableitung oder Handler oder ...?



  • Hallo,

    ich habe eine Klasse, die fast nur aus Nutzdaten besteht:

    struct DrawOperation {
       Indices* indices;
       int indexCount;
       vector<VBPass> vbPasses;
    };
    
    struct VBPass {
       int start;
       int count;
       ...
    };
    

    Ein Benutzer meines Codes/Bibliothek füllt die DrawOperation mit VBPass'es. In meinem Code wird dann über die vbPasses iteriert und pro vbpass wird gezeichnet. Das Problem: Es muss möglich sein, dass Usercode unmittelbar vor dem Zeichnen ausgeführt wird:

    //Das hier ist in meiner Bibliothek. Soll nicht änderbar sein durch den Anwender
    foreach vbpass in vbpasses
       setValuesXY
       call user code
       draw vbpass
    

    Wie mache ich das am besten, dass der Anwender meiner Lib Code schreiben kann, der vorm zeichnen ausgeführt wird? Füge ich in VBPass eine pure virtual Methode ein, so dass der User von VBPass ableiten muss? Oder gebe ich besser VBPass einen Handler mit Callback Funktionen? Oder was ganz anderes?



  • erweitere die struct VBPass um einen Member vom Typ boost::function<void()> und rufe diesen, falls er belegt ist, vor dem Zeichnen auf.

    #include <boost/function.hpp>
    struct VBPass {
       int start;
       int count;
       boost::function< void() > m_pre_step;
       ...
    };
    

    bei Dir in der Lib dann:

    if( vbpass.m_pre_step )
        vbpass.m_pre_step();
    // weiter mit Zeichnen von vbpass
    

    .. und verpasse VBPass einen oder mehrere für den Benutzer sinnvolle Konstruktoren.

    Gruß
    Werner



  • Tja, wenn das so einfach zu beantworten waere ...

    Füge ich in VBPass eine pure virtual Methode ein, so dass der User von VBPass ableiten muss?

    Ich nehm an, der Usercode wird ueber ne dll eingefuegt oder ?
    Bei allen anderen Themen haettest DU andere Probleme 🙂

    Klassen-Interfaces über Dll:
    Klar, schoener als CallBacks, aber Du nagelst den User auf einen Compiler fest, der zu deinem kompatible Klassen erzeugt. Generell nur bein selben hersteller, und meistens nur ueber minor versionen gegeben. Und man kann es mit COmpiler-Optionen fast immer aushebeln ...

    Würd ich nur machen, wenn Deine "User" im überschaubaren Rahmen bleiben ...

    Ciao ...



  • RHBaum schrieb:

    Tja, wenn das so einfach zu beantworten waere ...

    Füge ich in VBPass eine pure virtual Methode ein, so dass der User von VBPass ableiten muss?

    Ich nehm an, der Usercode wird ueber ne dll eingefuegt oder ?
    Bei allen anderen Themen haettest DU andere Probleme 🙂

    Ne, ist eine statische Lib. Was sind eigentlich die Pro/Kontra Argumente bzgl Handler Klasse (also sowas wie class Handler { virtual void pre() = 0; } und boost::function?



  • Warum macht ihr es eigentlich so kompliziert, DLL,...? Warum muss ein User Code einfügen? Wieso schreibt ihr nicht einfach Code nur für euch? Glaubt ihr wirklich das ihr das neue Framework für alle schreibt? Wenn man in nem forum nachfragen muss, wie das geht, ist das eher unwahrscheinlich.



  • MartinD schrieb:

    RHBaum schrieb:

    Tja, wenn das so einfach zu beantworten waere ...

    Füge ich in VBPass eine pure virtual Methode ein, so dass der User von VBPass ableiten muss?

    Ich nehm an, der Usercode wird ueber ne dll eingefuegt oder ?
    Bei allen anderen Themen haettest DU andere Probleme 🙂

    Ne, ist eine statische Lib. Was sind eigentlich die Pro/Kontra Argumente bzgl Handler Klasse (also sowas wie class Handler { virtual void pre() = 0; } und boost::function?

    Ein Interface ist "intrusive". Das heißt, jeder der User-Code bereitstellen will muss davon ableiten. Man kann dann genau eine Funktion in der abgeleiteten Klasse bereitstellen (eben die virtuelle), welche dann den User-Code bereitsstellt.
    Bei boost::function kannst Du quasi jede Funktion benutzen, welche irgendwie das passende Interface hat:

    //freie Funktion
    void function(VBPass & v)
    {...}
    
    //Methoden
    class VBPass_Utility
    {
    public:
        void fun1(VBPass & v);
        void fun2(VBPass & v);
    };
    
    //Funktor
    class VPass_Funktor
    {
    public:
        void operator()(VBPass & v);
    };
    
    //der boost::function-Typ
    typedef boost::function<void (VBPass & v)> callback_t;
    
    class DrawOperation
    {
    public:
        void do_stuff(callback_t const & user_code = callback_t())
        {
            if(user_code)
            {
                user_code(vpass_object); //callback aufrufen
            }
        }
    };
    
    //...
    DrawOperation drawer;
    
    drawer.do_stuff(&function); //benutzt freie funktion
    
    VBPass_Utility util;
    drawer.do_stuff(boost::bind(&VBPass_Utility::fun1, &util, _1); //benutzt util.fun1
    drawer.do_stuff(boost::bind(&VBPass_Utility::fun2, &util, _1); //benutzt util.fun2
    
    drawer.do_stuff(DrawOperation()); //benutzt DrawOperation::operator()
    
    drawer.do_stuff(); //fuehrt gar keinen User-Code aus
    

    Du bist also sehr frei darin zu bestimmen, wo genau dein Callback herkommen soll.



  • Ne, ist eine statische Lib.

    Wer compiliert die Lib ?
    Ist dieser ominöse "User" Teil eures Teams, also habts den 100% unter Kontrolle ?

    Was sind eigentlich die Pro/Kontra Argumente bzgl Handler Klasse (also sowas wie class Handler { virtual void pre() = 0; } und boost::function?

    Virtuelle funktionen sind quasi der c++ Ersatz fuer FunktionsPointer/CallBacks.
    Damit hasst aehnliche Vor und Nachteile wie bei ner C zu C++ disskussion 🙂
    Wer C-Callbacks intuitiv und natuerlich empfindet, der wird immer gegen ein Klasseninterface wettern.

    Aber ganz speziell:
    Vorteile Interface:
    intuitiverer Code -> einfachere Einarbeit -> wartbarer Code (wenn man OO denken kann/will )

    Vorteile Funktionspointer:
    C - kompatibel, dadurch binaer 100% spezifizierbar. Kannst Du so bauen, Implementationen mit irgendwelchen COmpilern gebaut werden koennen, die C konforme EInsprungpunkte bauen koennen. Neben C/C++ Compilern koennen das auch andere ...
    Dieser "Vorteil" kommt aber oft erst in spaeteren Ausbau eines Projects zum tragen. Für Projecte ist es ein absolutes K.O kriterium, wenn du den Compiler beim HauptProjekt änderst(beispielsweisse Migration VS Studio 2005 zu 2010) und alle "anderen" (meistens Plugins, oder Aufgaben-bezogene Dll's) müssen mit nachgezogen werden.
    Nen Simples neucompilieren kann zum No-Go werden, besonders wenn man den Sourcecode nicht kontrolliert, sondern der von nem Zulieferer gewartet wird ...

    Ciao ...



  • tödörödödö schrieb:

    Warum macht ihr es eigentlich so kompliziert, DLL,...? Warum muss ein User Code einfügen? Wieso schreibt ihr nicht einfach Code nur für euch? Glaubt ihr wirklich das ihr das neue Framework für alle schreibt? Wenn man in nem forum nachfragen muss, wie das geht, ist das eher unwahrscheinlich.

    Manche übertreiben es etwas, das stimmt schon.

    Andrerseits schreibt man auch verdammt oft Code, den man dann - meist im selben Projekt - selbst "verwendet". Und auch oft an mehr als nur einer Stelle. Da macht es schon Sinn halbwegs sauber zu arbeiten. Und da gehört IMO auch das Trennen von Zuständigkeiten dazu.

    Bzw. es bringt auch viel bezüglich Verständlichkeit/Wartbarkeit des Programms. Je sauberer die einzelnen Teile getrennt sind, desto einfacher kann man z.B. Fehler eingrenzen oder Änderungen machen.

    ----

    @MartinD
    Ich würde auch boost::function (bzw. std::function ) empfehlen. Warum? Weil es die Kopplung zwischen deiner Klasse und dem "verwendenden Code" reduziert. Und weil es praktischer ist. Mit virtuellen Funktionen müsste jeder User erstmal ne eigenen Klasse ableiten und die Funktion überschreiben. Das ist mehr Aufwand, als einfach nen (z.B. mit boost::bind / std::bind zusammengebauten) Funktor zu übergeben.


Log in to reply