Moderner C++-Weg für Callback
-
Hallo,
ich habe diesen Thread zum Anlass genommen, mich ancallback
zu versuchen. Ich habe gelesen, dass der moderne Weg über std::function führt, stimmt das?Mein Versuch führt allerdings zu einem Compilerfehler:
#include <functional> using namespace std; class calculator { private: float lhs; float rhs; public: calculator(float lhs, float rhs) : lhs(lhs), rhs(rhs) {} float add(float a, float b) { return a+b; } float sub(float a, float b) { return a-b; } float mul(float a, float b) { return a*b; } float div(float a, float b) { return a/b; } float calc( std::function<float (float,float)> func ) { return func(lhs,rhs); } }; int main() { calculator c(8,2); c.calc( &calculator::add ); // error C2064: Ausdruck ergibt keine Funktion, die 2 Argumente übernimmt }
Wie sähe das richtig aus? Bzw. würde man das überhaupt so implementieren, wenn man es via Callbacks machen will?
Danke im Voraus.
-
Warum sind
add()
etc. nicht-statische Memberfunktionen? Es macht keinen Sinn, sie auf einem Objekt aufzurufen. Definierst du sie als freie Funktionen ausserhalb der Klasse (oder als statische Memberfunktionen), funktioniert auch der Callback-Mechanismus.
-
Nexus schrieb:
Es macht keinen Sinn, sie auf einem Objekt aufzurufen. Definierst du sie als freie Funktionen ausserhalb der Klasse (oder als statische Memberfunktionen), funktioniert auch der Callback-Mechanismus.
Aber dann wäre doch die Klasse praktisch sinnlos oder? Also ich meine, weil solche Funktionen ja eigentlich zu einem Taschenrechner gehören.
-
Das liegt daran, dass du eine Funktion benutzt, die 2 Argumente erwartet (Stichwort Lambda-Expressions). Außerdem musst du bei einer Memberfunktion den Objektzeiger mitübergeben. Funktionieren tut es so:
int main() { using namespace std::placeholders; calculator c(8,2); c.calc(std::bind(&calculator::add, &c, _1, _2)); }
Edit:
Übrigens, Clang gibt für deinen Code eine schönere Fehlermeldung aus:error: no viable conversion from 'float (calculator::*)(float, float)' to 'std::function<float (float, float)>' c.calc(&calculator::add); ^~~~~~~~~~~~~~~~ /usr/include/c++/v1/functional:1137:5: note: candidate constructor not viable: no known conversion from 'float (calculator::*)(float, float)' to 'nullptr_t' for 1st argument; function(nullptr_t) _NOEXCEPT : __f_(0) {} ^ /usr/include/c++/v1/functional:1138:5: note: candidate constructor not viable: no known conversion from 'float (calculator::*)(float, float)' to 'const std::__1::function<float (float, float)> &' for 1st argument; function(const function&); ^ /usr/include/c++/v1/functional:1139:5: note: candidate constructor not viable: no known conversion from 'float (calculator::*)(float, float)' to 'std::__1::function<float (float, float)> &&' for 1st argument; function(function&&) _NOEXCEPT; ^ /usr/include/c++/v1/functional:1141:7: note: candidate template ignored: substitution failure [with _Fp = float (calculator::*)(float, float)] function(_Fp, ^ note: passing argument to parameter 'func' here float calc( std::function<float (float,float)> func )
-
int main() { using namespace std::placeholders; calculator c(8,2); c.calc(std::bind(&calculator::add, &c, _1, _2)); }
Ok jetzt sehen ich aber keinen Vorteil mehr gegenüber einfach
int main() { calculator c; c.add(8,2); }
Vielleicht verstehe ich aber auch einfach die Anwendungszwecke für Callbacks nicht.
-
Ein typischer Anwendungszweck von Callbacks ist, wie der Name schon erahnen lässt, wenn sich ein Objekt (oder eine Funktion) sich bei einem anderen zurückmeldet, dass etwas erledigt ist oder ein anderes Ereignis eingetreten ist.
class Calculator { public: void AddAsynchron(float a, float b, ResultCallback callback); };
Hier bietet die Calculator Klasse eine asynchrone Additionsmethode (jeder weiß ja wie lange das Addieren 2er floats dauert, wer will da schon sein user interface blockieren). Wie man sieht gibt diese Method gar nichts zurück. Das Ergebnis der Addition wird stattdessen an das Callback übergeben.
-
In diesem Beispiel macht es auch wie Nexus geschrieben hat überhaupt keinen Sinn. Da können Funktionspointer genauso herhalten (wobei ich nicht weiß, ob das noch "modern" ist?). Callbacks werden Interessant, wenn es z.B. um Delegates oder Events geht. Beispielsweise eine priority_queue mit std::function-Objekten, die nacheinander abgearbeitet werden sollen, da std::function verschiedene Arten von Funktionen aufnehmen kann (Mit Argumenten, ohne Argumenten, Methoden, Funktionen, Funktoren), was mit anderen Hilfsmitteln nur schwer umsetzbar ist. Das beste an der ganzen Sache ist, es ist alles Type-Safe, aufgrund der Auswertung zur Compilierzeit.
-
In deinem Beispiel könntest du einfach
8+2
stehen lassen. Ich sehe nicht den Sinn dieser Klasse.
-
Die Klasse hat den Sinn, durch eine triviale Gegebenheit ein Programmierkonzept und die Umsetzung mithilfe von std::function zu verstehen. Was bringt es da, unnötig komplizierte Klassen zu benutzen, wenn man nicht mal weiß, wie es mit einer einfachen geht?
-
brotbernd schrieb:
Ein typischer Anwendungszweck von Callbacks ist, wie der Name schon erahnen lässt, wenn sich ein Objekt (oder eine Funktion) sich bei einem anderen zurückmeldet, dass etwas erledigt ist oder ein anderes Ereignis eingetreten ist.
Ah, ok ich glaub nun habe ich es gerafft. Dann wäre folgendes z.B. ein guter Anwendungszweck:
#include <functional> using namespace std; class character { private: function<void(character&)> do_action; int health_points; public: void register_handler(unsigned refresh_rate, function<void(character&)> action) { do_action = action; } void heal_over_time() { health_points += 100; } }; void heal_over_time(character& c) // Alle X ms soll die Gesundheit des Characters um 100 erhöht werden. { c.heal_over_time(); } int main() { character c; c.register_handler( 100, &heal_over_time ); }
-
out schrieb:
brotbernd schrieb:
Ein typischer Anwendungszweck von Callbacks ist, wie der Name schon erahnen lässt, wenn sich ein Objekt (oder eine Funktion) sich bei einem anderen zurückmeldet, dass etwas erledigt ist oder ein anderes Ereignis eingetreten ist.
Ah, ok ich glaub nun habe ich es gerafft. Dann wäre folgendes z.B. ein guter Anwendungszweck:
...
Japs, das wäre ein Anwendungszweck. Aber ich verstehe nicht den Umweg über die Funktion außerhalb der Klasse. Warum nicht so?:
class character { private: function<void()> do_action; int health_points; public: void register_handler(unsigned refresh_rate, function<void()> action) { do_action = action; } void heal_over_time() {/*...*/} }; int main() { character c; c.register_handler( 100, std::bind(&character::heal_over_time, &c) ); }
-
Stimmt, aber im Endeffekt würde es ja einige
actions
geben, die würde ich dann alle in eine extra Klasse oder Namensraum auslagern, oder?
-
out schrieb:
Stimmt, aber im Endeffekt würde es ja einige
actions
geben, die würde ich dann alle in eine extra Klasse oder Namensraum auslagern, oder?Kommt stark auf die Umstände an. Zum Beispiel, ob klassisch OOP oder eher DOP programmiert wird, wie stark die Kapselung zwischen den verschiedenen Komponenten sind etc. pp, im Endeffekt hab ich aber fast immer eine art Actionhandler, der für eine Art von Actions zuständig ist. Mich interessiert aber auch, was andere zu dieser Frage zu sagen haben.
-
Und wenn man bei
class action
undcommand
angelangt ist, nimmt man einexecute
und landet glatt bei Java. Das sieht auf dem Papier und in UML total professionell aus, hat meist keinen Mehrwert persoenliche Erfahrung). http://steve-yegge.blogspot.de/2006/03/execution-in-kingdom-of-nouns.html