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 inconnect_function_x()
dasstd::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 vonconnect_function_x()
(nicht in der Funktion selbst).
Wenn du es also innerhalb deinerqt_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) vonqt_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 dasstd::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 konkretestd::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 Variablefunction_x
speichern. Oder alternativ stattstd::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 istIn 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.