Slots? Callbacks? Wrapper?



  • Ich habe eine Klasse - nennen wir sie Algo - geschrieben, die einen Algorithmus implementiert.
    Dieser Algorithmus soll von unterschiedlichen Programmen, die in C/C++ oder C++/Qt realisiert sind, eingesetzt werden.
    Im Laufe der Abarbeitung des Algorithmus müssen Funktionen aufgerufen werden, die außerhalb der Klasse liegen, zB. Netzwerkfunktionen (Windows-API, Linux, Qt-Netzwerkfunktionen), Funktionen die den Status visualieren, etc.

    Ich habe das so gelöst, dass die Klasse selbst member-Funktionen connect_function_x besitzt, die einen Funktionspointer erhalten, zB.:

    int algo::connect_function_x(void (*function)(unsigned int a, unsigned char* b))
    {
    	if (function == NULL) return 0;
    	else function_x = function;
    	return 1;
    }
    

    Nachdem ich eine Instanz der Klasse erzeugt und die Funktionen zum "verbinden" der Funktionen aufgerufen habe, kann ich im Algorithmus run_algorithm() dann die Funktion dann mit aufrufen:

    algo inst;
    inst.connect_function_x(function_to_be_called);
    inst.run_algorithm(); // calls function_to_be_called() via function_x()
    

    Möchte ich diese Klasse nun in C++/Qt einsetzen, wo der Algortihmus in einem Qt-Slot benutzt wird, bekomme ich folgenden Fehler:
    argument of type "void(qt_GUI::*)(unsigned int a, unsigned char* b)" is incompatible with parameter of type "void (*)(unsigned int a, unsigned char* b)".
    Ich glaube zu verstehen warum ich den Fehler erhalte, jedoch stelle ich mir jetzt die Frage ob ich überhaupt den richtigen Ansatz zur Lösung meines Problems gewählt habe (sowohl was die Implementierung der Klasse angeht, als auch wie ich dann die Pointer bereitstelle).

    In Qt würde man so etwas wohl über signals/slots lösen (denke ich)? Brauche ich std::bind()? Wie macht man sowas "richtig"?



  • Die Funktionssignatur void (*function)(...) ist nur für freie Funktionen oder statische Klassenfunktionen benutzbar, nicht für nicht-statische Klassenfunktionen (wie z.B. void qt_GUI::Memberfunction(...)).

    Du solltest daher besser std::function<...> benutzen (und in Verbindung mit std::bind dann die Klasseninstanz, z.B. this, übergeben).



  • Ich definiere meinen function pointer also als
    std::function<void(unsigned int a, unsigned char* b)> function_x = nullptr;
    und meine "Verbindungsfunktion" als
    algo::connect_function_x(std::function<void(unsigned int a, unsigned char* n)> function)();.

    Aber wie verbinde ich nun?
    Wenn ich die Referenz die du verlinkt hast (Danke!) richtig gelesen habe, müsste ich in connect_function_x() das std::bind ausführen,
    aber wie genau? Welche Klasseninstanz muss ich da übergeben und warum?

    Ich hätte es naiv einfach mit function_x = std::bind(function, _1, _2); probiert.



  • Das std::bind benutzt du beim Aufruf von connect_function_x() (nicht in der Funktion selbst).
    Wenn du es also innerhalb deiner qt_GUI-Klasse aufrufst:

    void qt_GUI::run_algorithm()
    {
      algo inst;
      inst.connect_function_x(std::bind(&qt_GUI::function_to_be_called, this));
      inst.run_algorithm();
    }
    

    Und function_to_be_called ist dabei ebenfalls eine Klassenfunktion (Member) von qt_GUI:

    void qt_GUI::function_to_be_called(unsigned int a, unsigned char* b)
    {
      // ...
    }
    

    Intern ist bei einer nicht-statischen Klassenfunktion der erste Parameter immer die (zu benutzende) Klasseninstanz.
    Bei einer statischen Klassenfunktion benötigst du das std::bind nicht, da es dort ja keine Instanz (this) gibt.

    PS: Einfacher ist es auch (zu lesen), wenn du dir einen typedef bzw. using für deine konkrete std::function<...> erzeugst.



  • Ich habe jetzt meine Klasse als

    class algo
    {
    private:
        std::function<void(unsigned int a, unsigned char* b)> function_x = nullptr;
    
    public:
        int connect_function_x(std::function<void(unsigned int a, unsigned char* b)>);
    }
    
    int algo::connect_function_x(std::function<void(unsigned int a, unsigned char* b)> function)
    {
        if (function == nullptr) return 0;
        else function_x = function;
        return 1;
    }
    

    Qt slot:

    void Qt_GUI::Qt_slot()
    {
        algo inst;
        inst.connect_function_x(std::bind(&Qt_GUI::function_to_be_called, this));
    }
    

    Beim Aufruf von std::bind() bekomme ich den Fehler
    no suitable user-defined conversion from "std::_Binder<std::_Unforced, void (Qt_GUI::*)(unsigned int a, unsigned char* b), Qt_GUI *>" to "std::function<void (unsigned int a, unsigned char* b)>" exists

    (Sorry, bin jetzt schon ein wenig im dummy mode...)



  • Bei dem bind fehlen ja auch die beiden Parameter, also hier "_1" und "_2".



  • @Jockelx 🤦🏼♂ Danke...



  • Gibt es eine Möglichkeit das bind in die Klasse zu verlagern?
    Die Intention wäre, dass die Verbindungsfunktionen unabhängig davon ob sie aus einer fremden Klasse oder "frei" aufgerufen werden, gleich aussehen, also nur eine(n) Funktion(spointer) erhalten.



  • @DaRaRa sagte in Slots? Callbacks? Wrapper?:

    Gibt es eine Möglichkeit das bind in die Klasse zu verlagern?
    Die Intention wäre, dass die Verbindungsfunktionen unabhängig davon ob sie aus einer fremden Klasse oder "frei" aufgerufen werden, gleich aussehen, also nur eine(n) Funktion(spointer) erhalten.

    Verstehe ich noch nicht ganz. Dein Algorithmus hat doch kein std::bind etc. mehr drin. Also sprich connect_function_x weiß nicht mehr, ob es sich um eine member funktion oder nicht handelt. Beim Aufruf, also aktuell in der Qt_slot Methode, wird es aktuell gemacht. Da ist doch gut, weil deine Qt_Gui weiß ja, ob es sich um eine member funktion handelt oder nicht.

    Ggf. kannst du nochmal konkret das Problem zeigen. Hast du Code, der quasi aktuell nicht funktioniert? Oder was stört dich genau?

    P.S. Sofern du C++20 verwendest, kannst du auch std::bind_front nutzen. Das bindet nur den ersten Parameter und funktioniert daher ohne die Placeholder. Siehe https://en.cppreference.com/w/cpp/utility/functional/bind_front.
    Damit würde deine Code von oben funktionieren, wo du bei std::bin noch die placeholder hinzufügen musstest.



  • Sorry, das mit den Placeholdern hatte ich vergessen. Und danke @Leon0402 für den Link auf std::bind_front.



  • Danke an alle für die Antworten!

    Ich habe mich falsch ausgedrückt:
    Was mich stört ist, dass die Aufrufe der connect_*-Funktionen anders aussehen, je nachdem von welcher Klasse aus sie aufgerufen werden:
    Rufe ich sie aus dem Qt-Kontext auf lautet der Aufruf
    algo.connect_function_x(std::bind(&Qt_GUI::function_x, this, _1, _2));
    aus dem freien Kontext
    algo.connect_function_x(function_x);
    aber da führt wohl kein Weg daran vorbei.?



  • Ich bastel grad an was Ähnlichem, vielleicht kannste was damit anfangen:

    #include <string>
    #include <functional>
    #include <iostream>
    
    // Test signature
    using Callback_t = std::function<void( unsigned int , std::string const& value )>;
    
    /***************************************************************************************************
    *                                                                                                  *
    * Pointer to non-const member function of non-const Object                                         *
    *                                                                                                  *
    ***************************************************************************************************/
    template<typename ReturnType, typename ObjectType, typename ...Params>
    std::function<ReturnType( Params...)> make_callback( ObjectType& obj, ReturnType (ObjectType::*mem_fun)( Params... ) )
    {
       auto Invoker = [&obj, mem_fun]( Params... params ) -> ReturnType
       {
          return std::invoke( mem_fun, obj, std::forward<Params>( params )... );
       };
       return std::function<ReturnType( Params... )>( Invoker );
    }
    
    /***************************************************************************************************
    *                                                                                                  *
    * Pointer to const member function of non-const Object                                             *
    *                                                                                                  *
    ***************************************************************************************************/
    template<typename ReturnType, typename ObjectType, typename ...Params>
    std::function<ReturnType( Params...)> make_callback( ObjectType& obj, ReturnType (ObjectType::*mem_fun)( Params... ) const )
    {
       auto Invoker = [&obj, mem_fun]( Params... params ) -> ReturnType
       {
          return std::invoke( mem_fun, obj, std::forward<Params>( params )... );
       };
       return std::function<ReturnType( Params... )>( Invoker );
    }
    
    /***************************************************************************************************
    *                                                                                                  *
    * Pointer to const member function of const Object                                                 *
    *                                                                                                  *
    ***************************************************************************************************/
    template<typename ReturnType, typename ObjectType, typename ...Params>
    std::function<ReturnType( Params...)> make_callback( ObjectType const& obj, ReturnType (ObjectType::*mem_fun)( Params... ) const )
    {
       auto Invoker = [&obj, mem_fun]( Params... params ) -> ReturnType
       {
          return std::invoke( mem_fun, obj, std::forward<Params>( params )... );
       };
       return std::function<ReturnType( Params... )>( Invoker );
    }
    
    /***************************************************************************************************
    *                                                                                                  *
    * Free function                                                                                    *
    *                                                                                                  *
    ***************************************************************************************************/
    template<typename ReturnType, typename ...Params>
    std::function<ReturnType( Params...)> make_callback( ReturnType (*free_fun)( Params... ) )
    {
       return std::function<ReturnType( Params... )>( free_fun );
    }
    
    
    
    struct Test
    {
    	void function(  unsigned int id, std::string const& value ) const
    	{
    		std::cout << "member function: Id: " << id << ", value: " << value << "\n";
    	}
    
    };
    
    
    
    void free_func( unsigned int id, std::string const& value ) 
    {
    		std::cout << "free function: Id: " << id << ", value: " << value << "\n";
    }
    
    void int main()
    {
    	Test s0;
    	Test const s1;
    
    	Callback_t cb0 = make_callback( s0, &Test::function );
    	cb0( 4710, std::string( "Hello World" ) );
    	
    	Callback_t cb1 = make_callback( s1, &Test::function );
    	cb1( 4711, std::string( "Hello World" ) );
    
    	Callback_t cb2 = make_callback( &free_func );
    	cb2( 4712, std::string( "Hello World" ) );
    }
    


  • @DaRaRa sagte in Slots? Callbacks? Wrapper?:

    Danke an alle für die Antworten!

    Ich habe mich falsch ausgedrückt:
    Was mich stört ist, dass die Aufrufe der connect_*-Funktionen anders aussehen, je nachdem von welcher Klasse aus sie aufgerufen werden:
    Rufe ich sie aus dem Qt-Kontext auf lautet der Aufruf
    algo.connect_function_x(std::bind(&Qt_GUI::function_x, this, _1, _2));
    aus dem freien Kontext
    algo.connect_function_x(function_x);
    aber da führt wohl kein Weg daran vorbei.?

    Naja du musst es ja nicht in einer Zeile machen. Also sprich, du kannst auch den std::bind Aufruf irgendwo früher machen und in ner Variable function_x speichern. Oder alternativ statt std::bind auch ein Lambda nutzen.

    Aber vlt. versuchst du nochmal zu erklären / herauszufinden, warum es dich stört, dass die Aufrufe anders aussehen. Grundsätzlich ist das ja erstmal erwartbar, dass der Aufruf anders aussieht, wenn man unterschiedliche Dinge macht. Okay nehmen wir mal die Besonderheit mit std::bind und den Member Funktionen weg. Selbst dann wäre die Syntax ja mindestens sowas wie:

    algo.connect_function_x(&Qt_GUI::function_x);
    algo.connect_function_x(&function_x);
    

    Ist ja irgendwie auch logisch ... irgendwie musst du ha zwischen einer freien Funktion function_x und einer Funktion in einer Klasse, Namespace etc. unterscheiden können. Deswegen spricht aus meiner Sicht erstmal nichts grundsätzlich dagegen, dass die Aufrufe anders aussehen. Im Gegenteil das ist sogar sehr sinnvoll 😃

    Die eigentliche Syntax ist etwas komplexer. Aber auch das ergibt grundsätzlich Sinn. Member Funktionen müssen ja auf irgendeinem Objekt aufgerufen werden. Das wäre oben in dem Call ja gar nicht encodiert. Das kannst du aber wie gesagt auch schöner machen in C++20*:

    algo.connect_function_x(std::bind_front(&Qt_GUI::function_x, this));
    

    Wenn dich trotzdem irgendwas stört, dann gibt es dafür zwei recht wahrscheinliche Gründe:
    a) Du verschweigst uns irgendein "echtes" Problem noch, welches sich aus der unterschiedlichen Syntax ergibt
    b) Du hast irgendwas nicht so ganz verstanden, sodass es dir seltsam / unnötig komplex vorkommt, dass die Syntax anders ist

    In beiden Fällen ergibt es Sinn, wenn du versuchst genauer zu erklären, was genau dein Problem ist.

    • Mir würde so eine Syntax noch ganz gut gefallen:
    algo.connect_function_x(&this->function_x);
    

    Das wird meines Wissens nach aber nicht unterstützt. Also quasi ein "automatisches binden". Das macht dann aber vermutlich in anderen Fällen wieder Probleme, wo man das nicht will.



  • @Leon0402: Du meinst std::bind_front?! 😉

    // ab C++20
    algo.connect_function_x(std::bind_front(&Qt_GUI::function_x, this));
    


  • @Th69 sagte in Slots? Callbacks? Wrapper?:

    @Leon0402: Du meinst std::bind_front?! 😉

    // ab C++20
    algo.connect_function_x(std::bind_front(&Qt_GUI::function_x, this));
    

    Da ist man mal einmal unaufmerksam 😃 Danke, ist gefixt.


Anmelden zum Antworten