C++03 konforme Alternative zu __closure?



  • Hallo,

    gibt es eine C++03 konforme Alternative zu dem __closure Konstrukt? Ich brauche eine universelles Publisher/Subscriber Framework, das ohne borland-spezifische Spracherweiterungen auskommt. Pointer-to-member Funktionen ließen sich bisher einfach mit der __closure Erweiterung erschlagen, aber jetzt brauchen wir eine standardkonforme C++ Lösung.

    1. boost::signals2
      Wird vom Compiler nicht unterstützt. RAD Studio 10.1 liefert zwar die boost Bibliotheken 1.39 mit, allerdings fehlt dort signals2.

    2. boost::signal
      Ist in den boost Bibliothken 1.39 dabei, wird vom Compiler auch übersetzt, löst aber bei Benutzung eine Access Violation aus.

    3. Eigenbau mit boost::bind/boost::function
      Totale Katastrophe. Im besten Fall bekomme ich einen internen Compiler Fehler, im schlimmsten Fall hängt der Compiler oder die IDE beendet sich.

    Im Grunde brauche ich sowas:

    #include <vector>
    
    struct S1
    {
       void f( int x )
       {
       }
    };
    
    struct S2
    {
       void f( int x )
       {
       }
    };
    
    int main()
    {
       S1 s1;
       S2 s2;
    
       vector v<MAGIC_HERE> v;
       v.push_back( MAGIC_HERE( &s1, &S1::f ) );
       v.push_back( MAGIC_HERE( &s2, &S2::f ) );
    
       for( vector v<MAGIC_HERE>::const_iterator i = v.begin(); i != v.end(); ++v )
       {
          (*i)( 123 ); // ruft s1.f( 123 ) und s2.f( 123 ) auf
       }
    }
    

    Weiß jemand Rat?



  • Ob das comfortabel ist, sei mal dahingestellt, aber ich habe mir vor einiger Zeit mal etwas gebastelt, was solche __closure Konstrukte nachbildet:

    #ifndef _CALLBACK_H
    #define _CALLBACK_H
    /******************************************************************************/
    //Makro zur Definition von Callbackklassen
    //Name     = Name der CallBackKlasse
    //Params   = Auflistung der zu übergebenen Parameter mit Typangabe (Typ Name)
    //           Die auflistung ist in Klammern zu setzen, die Parameter sind
    //           mit Komma zu trennen
    //VarNames = Übergebene Parameter ohne Typangabe, d.h. hier noch einmal alle
    //           Parameter aufzählen ohne Typ und Modifizierer.
    //
    //*******************************************************************************
    
    #define DEFINECALLBACK(Name,Params,VarNames) \
    	class Name{\
    	public:\
    		virtual void operator() Params =0;\
    	};\
    	\
    	template <class TEvClass> class CB_##Name : public Name{\
    	public:\
    		typedef void (TEvClass::*Event_t) Params;\
        CB_##Name(TEvClass* pEvClass, Event_t pEvMethod): m_pClass(pEvClass),m_pEvent(pEvMethod){}\
    	protected:\
    		void operator() Params {(m_pClass->*m_pEvent) VarNames;}\
    	private:\
    		TEvClass*  m_pClass;\
    		Event_t    m_pEvent;\
    	};
    /********************************************************************************/
    // Makro zur Erzeugung des Callbacks
    // Name		 = Name der Callbackklasse (von DEFINECALLBACK)
    // ClassName = Klassename, zur der die aufzurufende Methode gehört
    // Method    = Name der aufzurufenden Methode
    
    #define CREATECALLBACK(Name,ClassName,Method) new CB_##Name<ClassName>(this,&ClassName::Method)
    /********************************************************************************/
    
    #endif
    

    Beispiel:
    Das Event soll "DataEvent" heissen und hat als Parameter einen Zeiger
    auf Daten und einen integer-Wert, der die Anzahl angeben soll:

    DEFINECALLBACK(DataEvent,(void *Data, int count),(Data, count))
    

    Das Objekt, welches das Event aufrufen soll, sei "reader".

    Eine Klasse "CSerialData" soll eine Methode "OnData" erhalten, die aufgerufen
    werden soll. Diese soll vom Typ "DataEvent" sein, daher sieht sie also so aus:

    void OnData (void *Data, int count)
    

    OnData soll von Objekt reader aufgerufen werden. Dazu muss dieses dann eine
    Membervariable definieren, die vom Typ DataEvent ist:

    DataEvent *FOnData;
    

    Dafür am besten eine Set-funktion definieren:

    void SetOnData(DataEvent *Value)
    { 
     delete FOnData; //muss im Konstruktor mit 0 initialisiert werden!!!
    		 //sorgt dafür, das bei Neuzuweisung das alte Event
    	         //gelöscht wird.
      FOnData = Value;
    }
    

    Die Zuweisung wird nun von der Klasse ausgeführt, die die Methode "OnData" enthält,
    im Beispiel also innerhalb der Klasse CSerialData:

    reader.SetOnData(CREATECALLBACK(DataEvent,CSerialData,OnData));
    

    Hier einmal als kompletter Code:

    DEFINECALLBACK(DataEvent,(void *Data, int count),(Data, count));
    
     class CReader
     {
     public:
    	 CReader() : FOnData(0) {}
    	 ~CReader() { SetOnData(0);};
    
    	 void SetOnData(DataEvent *Value)
    	 { 
    		 delete FOnData; 
    		 FOnData = Value;
    	 }	 
    
       void Datenempfangen()
    	 {
    		 //.....
    		 if (FOnData)
    		 {
    		   (*FOnData)(Data,Len);
    		 }
    	 }
    
     private:
       DataEvent *FOnData;  
     };
    
     class CSerialData
     {
     public:
       CSerialData()
    	 {
    		 reader.SetOnData(CREATECALLBACK(DataEvent,CSerialData,OnData)); 
    	 }
     private:
    	 CReader reader;
    
    	 void OnData(void *Data, int count)
    	 {
    		 //.....
    	 }
     };
    

    Ev. ist das ja was für dich.



  • DocShoe schrieb:

    1. Eigenbau mit boost::bind/boost::function
      Totale Katastrophe. Im besten Fall bekomme ich einen internen Compiler Fehler, im schlimmsten Fall hängt der Compiler oder die IDE beendet sich.

    Auch mit std::function<> ? Oder ist das immer noch dieselbe Implementierung wie boost::function<> ?

    Ich erinnere mich, daß ich früher (RAD Studio XE) boost::function<> und std::bind() ohne Probleme benutzen konnte. Wenn das in neueren Versionen nicht mehr geht, hat wohl jemand das QA-Department wegrationalisiert.

    Wie dem auch sei: den Klassiker zum Thema kennst du vermutlich?
    https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
    Und dieses Follow-up?
    https://www.codeproject.com/Articles/11015/The-Impossibly-Fast-C-Delegates
    Vielleicht ist das ja eine stabilere Grundlage für dich.



  • Danke für die Hinweise,

    im Moment fehlt mir leider die Zeit, mir das im Detail anzugucken.

    @Burkhi:
    Was mir an deiner Lösung nicht so gefällt ist das Hantieren mit besitzenden Zeigern und die damit notwendige Speicherverwaltung. Evtl. kann man das in einen shared_ptr verpacken.

    @Audacia:
    Ja, kenne ich, hab mir da auch ein paar Sachen abgeguckt. Was mir an den impossibly fast C++ delegates nicht gefällt ist, dass der Typ der Memberfunktion als Template Parameter übergeben wird und die Konstruktiond dadurch etwas umständlich wird. Aber das kriegt man sicher durch nen Wrapper hin. Muss mir das aber noch mal genauer angucken.

    Und was das RAD Studio angeht:
    Es hatte wohl einen schlechten Tag. Heute war´s besser drauf und ist zumindest nicht abgestürzt, als ich boost::bind/function benutzt habe. Übersetzen tut´s trotzdem nicht 😕



  • [quote = "DocShoe"]Was mir an den impossibly fast C++ delegates nicht gefällt ist, dass der Typ der Memberfunktion als Template Parameter übergeben wird und die Konstruktiond dadurch etwas umständlich wird.Aber das kriegt man sicher durch nen Wrapper hin.[/quote]Leider nur mit Makro und decltype() .

    Hier mal ein auf den "impossibly fast delegates" aufbauender Versuch, der mit VC++ und g++ kompiliert:

    #include <type_traits>
    #include <cstdio>
    
    template <typename FuncT>
        class Delegate;
    template <typename R, typename... Args>
        class Delegate<R(Args...)>
    {
    private:
        typedef R (*Stub_)(void*, Args...);
    
        void* data;
        Stub_ code;
    
        Delegate(void* _data, Stub_ _code)
            : data(_data), code(_code)
        {
        }
        template <typename C, R (C::*Func)(Args...)>
            static R methodStub(void* obj, Args... args)
        {
            return (static_cast<C*>(obj)->*Func)(args...);
        }
        template <typename C, R (C::*Func)(Args...) const>
            static R constMethodStub(void* obj, Args... args)
        {
            return (static_cast<const C*>(obj)->*Func)(args...);
        }
        template <typename C, R (*Func)(C*, Args...)>
            static R functionStub(void* obj, Args... args)
        {
            return Func(static_cast<C*>(obj), args...);
        }
    
    public:
        Delegate(void)
            : data(nullptr), code(nullptr)
        {
        }
        Delegate(const Delegate& rhs) = default;
        Delegate(Delegate&& rhs)
            : data(rhs.data), code(rhs.code)
        {
            rhs.data = nullptr;
            rhs.code = nullptr;
        }
        Delegate& operator =(const Delegate& rhs) = default;
        Delegate& operator =(Delegate&& rhs)
        {
            data = rhs.data;
            code = rhs.code;
            rhs.data = nullptr;
            rhs.code = nullptr;
            return *this;
        }
    
        template <typename C, R (C::*Func)(Args...)>
            static Delegate<R(Args...)> fromMethod(C* obj)
        {
            return Delegate<R(Args...)>(static_cast<void*>(const_cast<typename std::remove_const<C>::type*>(obj)), methodStub<C, Func>);
        }
        template <typename C, R (C::*Func)(Args...) const>
            static Delegate<R(Args...)> fromConstMethod(const C* obj)
        {
            return Delegate<R(Args...)>(static_cast<void*>(const_cast<C*>(obj)), constMethodStub<C, Func>);
        }
        template <typename C, R (*Func)(C*, Args...)>
            static Delegate<R(Args...)> fromFunction(C* obj)
        {
            return Delegate<R(Args...)>(static_cast<void*>(const_cast<typename std::remove_const<C>::type*>(obj)), functionStub<C, Func>);
        }
    
        R operator ()(Args... args) const
        {
            return code(data, args...);
        }
    };
    
    template <typename FuncT>
        struct DelegateHelper_;
    template <typename C, typename R, typename... Args>
        struct DelegateHelper_<R (C::*)(Args...)>
    {
        template <R (C::*Func)(Args...)>
            static Delegate<R(Args...)> makeDelegate(C* obj)
        {
            return Delegate<R(Args...)>::template fromMethod<C, Func>(obj);
        }
    };
    template <typename C, typename R, typename... Args>
        struct DelegateHelper_<R (C::*)(Args...) const>
    {
        template <R (C::*Func)(Args...) const>
            static Delegate<R(Args...)> makeDelegate(const C* obj)
        {
            return Delegate<R(Args...)>::template fromConstMethod<C, Func>(obj);
        }
    };
    template <typename C, typename R, typename... Args>
        struct DelegateHelper_<R (*)(C*, Args...)>
    {
        template <R (*Func)(C*, Args...)>
            static Delegate<R(Args...)> makeDelegate(C* obj)
        {
            return Delegate<R(Args...)>::template fromFunction<C, Func>(obj);
        }
    };
    
    #define DELEGATE_METHOD(obj,memfun) (DelegateHelper_<decltype(memfun)>::template makeDelegate<memfun>(obj))
    #define DELEGATE_FUNCTION(obj,fun) (DelegateHelper_<decltype(fun)*>::template makeDelegate<fun>(obj))
    

    Test:

    class Test
    {
    private:
        float f;
    
    public:
        Test(float _f) : f(_f) { }
        float foo(double d) { f += float(d); return f; }
        float bar(double d) const { return f + float(d); }
    };
    
    float freeFoo(Test* self, double d) { return self->foo(d); }
    float freeBar(const Test* self, double d) { return self->bar(d); }
    
    int main(void)
    {
        Test t(42.f);
    
        Delegate<float(double)> d1, d2, d3, d4;
    
        d1 = DELEGATE_METHOD(&t, &Test::foo);
        d2 = DELEGATE_METHOD(&t, &Test::bar);
        d3 = DELEGATE_FUNCTION(&t, freeFoo);
        d4 = DELEGATE_FUNCTION(&t, freeBar);
    
        std::printf("d1(%g) = %g\n", 1.f, d1(1.f));
        std::printf("d2(%g) = %g\n", 1.f, d2(1.f));
        std::printf("d3(%g) = %g\n", 1.f, d3(1.f));
        std::printf("d4(%g) = %g\n", 1.f, d4(1.f));
    }
    

    Mit decltype sollte der BCC eigentlich klarkommen. Ich überlasse dir die Fleißarbeit, die variadic templates und Default-Kopierkonstruktor und -Zuweisungsoperator nach C++03 zu portieren 🙂



  • DocShoe schrieb:

    ...

    @Burkhi:
    Was mir an deiner Lösung nicht so gefällt ist das Hantieren mit besitzenden Zeigern und die damit notwendige Speicherverwaltung. Evtl. kann man das in einen shared_ptr verpacken.

    ...

    Ich hatte das in der Zwischenzeit auch schon etwas verbessert. 😉



  • DocShoe schrieb:

    Und was das RAD Studio angeht:
    Es hatte wohl einen schlechten Tag. Heute war´s besser drauf und ist zumindest nicht abgestürzt, als ich boost::bind/function benutzt habe. Übersetzen tut´s trotzdem nicht 😕

    Ich wüsste zwar nicht wann das RAD Studio überhaupt einmal einen guten Tag hätte, aber in unseren Projekt nutzten wir bind/function (glaube seit dem XE4), wobei die Schreibweise [z.B. ] in Einzelfall nicht dem Standard entspricht.

    So nutzen wir z.B. boost::function1(X, Y) statt std::function(X (Y)) [X und Y durch den entsprechenden Rückgabetyp und Parametertyp ersetzen].



  • Ich habe mich jetzt noch mal mit boost::function beschäftigt, der Fehler lag bei mir. Wenn man sie richtig benutzt funktionieren sie auch 😉