Threads, Member Function Pointer und Type Erasure
-
Ich moechte Memberfunktionen innerhalb eines Threads ausfuehren. Dafuer gibt es
std::thread. Jedoch soll beim Aufruf des Destruktors automatisch eine Funktionstopaufgerufen werden und mittelsjoinauf das Ende des Threads gewartet werden. Dazu habe ich mir folgendes einfaches Template geschrieben:template<typename T, void (T::*Run)(), void (T::*Stop)()> struct service { service(T& runnable) : runnable(runnable), th(Run, std::ref(runnable)){} ~service(){ (runnable.*Stop)(); if (th.joinable()) th.join(); } private: T& runnable; std::thread th; };Es kann wie folgt aufgerufen werden:
struct A { void run() { .. } void stop() { .. } }; struct B { void exec() { .. } void halt() { .. } }; A a; B b; service<A, &A::run, &A::stop> service_a(a); service<B, &B::exec, &B::halt> service_b(b);Jetzt moechte ich nicht staendig ein neuen Typ mittels service-Template erstellen, sondern aehnlich wie bei
std::threadeinfach Funktion und Objekt uebergeben. Beispiel:service2 service_a(&A::stop, &A::run, a)Als Loesung bietet sich Type Erasure an. Als Vorlage diente http://www.cplusplus.com/forum/articles/18756/ :
class service2 { struct service_concept { virtual ~service_concept() {} }; template<typename T, void (T::*Run)(), void (T::*Stop)()> struct service_model : service_concept { service_model(T& runnable) : runnable(runnable), th(Run, std::ref(runnable)){} virtual ~service_model(){ (runnable.*Stop)(); if (th.joinable()) th.join(); } private: T& runnable; std::thread th; }; std::shared_ptr<service_concept> obj; public: template<typename T, void (T::*Run)(), void (T::*Stop)() > service2(Stop s, Run, T& runnable) : obj( new service_model<T, Run, Stop>(runnable) ){} };Leider bekomme ich als Fehler in Zeile 27
error C2061: syntax error : identifier 'Stop'. Frage: Wie biege ich es so hin, dass es uebersetzt wird?Leider kann ich gerade nicht nachschauen, wie
std::function,std::bindbzw.std::threaddas machen. Unter Visual Studio 11 Beta verwirren mich die Makros fuer Varadic Templates. Alternativ ueberlege ich gerade, wie eine Loesung mittelsstd::functionausschaut. Grossartig darueber nachgedacht habe ich jetzt noch nicht.
-
struct service { typedef std::function<void ()> action; template <class F> service(F &&run, action stop) : stop(std::move(stop)), th(std::forward<F>(run)){} ~service(){ stop(); if (th.joinable()) th.join(); } private: action stop; std::thread th; };
-
Wenn du die Memberfunktionszeiger als Funktionsargumente (und nicht Templateargumente) übergibst, sind sie im Konstruktor keine konstanten Ausdrücke mehr und können deshalb nicht als Templateargumente von
service_modelverwendet werden.Du kannst entweder durchgehend nur noch
Tals Templateparameter haben und die Memberfunktionszeiger als Variablen speichern, oder du schreibst dir ein Funktionstemplate, das die Memberfunktionszeiger als Templateargumente nimmt:auto my_service = create_service<&A::run, &A::stop>(a);Mit
std::functionbist du grundsätzlich viel flexibler, da du nicht auf Memberfunktionszeiger beschränkt bist. Allerdings kann die Laufzeit aufgrund der dynamisch gespeicherten Funktionen minimal schlechter werden, was man aber durch direktes Hardwiren durch Templates beheben kann (d.h. der Typ der Funktionen wäre dannFstattstd::function<void()>). Zudem ist die Aufrufsyntax ein wenig komplizierter, da du wahrscheinlichbind()odermem_fn()benutzen wirst.Ausserdem: Verwende
std::unique_ptrstattstd::shared_ptr, du hast schliesslich keinen geteilten Besitz.
-
Nexus schrieb:
Mit
std::functionbist du grundsätzlich viel flexibler, da du nicht auf Memberfunktionszeiger beschränkt bist. Allerdings kann die Laufzeit aufgrund der dynamisch gespeicherten Funktionen minimal schlechter werdenWarum holst du die Performance-Keule heraus? Das ist total unnötig und spielt hier überhaupt keine Rolle, weil das Hantieren mit Threads schon um Größenordnungen teurer ist.
-
TyRoXx schrieb:
Warum holst du die Performance-Keule heraus? Das ist total unnötig
Warum "Keule"? Ich habe das nur als Randnotiz hinzugefügt, damit knivil, der den
std::function-Ansatz nicht kennt, etwas mehr darüber erfährt. Keine Ahnung, was du für ein Problem damit hast, zumal ich extra "minimal" geschrieben habe.TyRoXx schrieb:
und spielt hier überhaupt keine Rolle, weil das Hantieren mit Threads schon um Größenordnungen teurer ist.
Schon mal dran gedacht, dass man in ähnliche Situationen kommen könnte, wo es eine Rolle spielt?

-
Wenn du die Memberfunktionszeiger als Funktionsargumente (und nicht Templateargumente) übergibst, sind sie im Konstruktor keine konstanten Ausdrücke mehr und können deshalb nicht als Templateargumente von service_model verwendet werden.
Ja, dass habe ich auch gemerkt. Ziel ist es, auf Template-Parameter a la create_service<&A::run, &A::stop>(a) zu verzichten. Meine bisheriege Loesung schaut wie folgt aus:
class service2 { struct service_concept { virtual ~service_concept() {} }; template<typename T> struct service_model : service_concept { service_model(T& runnable, void (T::*stop)(), void (T::*run)() ) : runnable(runnable), th(run, std::ref(runnable)), stop(stop), run(run) { std::cout << "constructor called " << typeid(*this).name() << std::endl; } virtual ~service_model(){ std::cout << "destructor called " << typeid(*this).name() << std::endl; (runnable.*stop)(); if (th.joinable()) th.join(); } private: T& runnable; std::thread th; void (T::*stop)(); void (T::*run)(); }; std::unique_ptr<service_concept> obj; public: template<typename T> service2(void (T::*stop)(),void (T::*run)(), T& runnable) : obj( new service_model<T>(std::forward<T>(runnable), stop, run) ) { } };Leider wird unter Visual Studio 11 Beta gleich nach dem Konstruktor der Destruktor aufgerufen. Und ich weiss nicht warum. Vielleicht kann mir jemand helfen. Der Debugger hat mir nicht geholfen. Ein Minimalbeispiel:
struct type_a { type_a() { std::cout << "constructor type_A" << '\n'; halt = false; } void run() { while(!halt){ std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::cout << "type_A thread" << '\n'; } } void stop() { halt = true; } private: std::atomic<bool> halt; }; struct type_b { type_b() { std::cout << "constructor type_B" << '\n'; halt = false; } void run() { while (!halt) { std::this_thread::sleep_for(std::chrono::milliseconds(600)); std::cout << "type_B thread" << '\n'; } } void stop() { halt = true; } private: std::atomic<bool> halt; }; int _tmain(int argc, _TCHAR* argv[]) { type_a a; type_b b; service2(&type_a::stop, &type_a::run, a); std::this_thread::sleep_for(std::chrono::seconds(5)); service2(&type_b::stop, &type_b::run, b); std::this_thread::sleep_for(std::chrono::seconds(5)); return 0; }Nochmals zum Ziel:
service2soll moeglichst mitservice2(stop_fn, run_fun, obj, param, ...)aufgerufen werden, wobei stop_fn und run_fn Memberfunktionen von obj sind. obj und param... sollen anstd::threaduebergeben werden, obj soll nicht gemoved werden, da ich mit dem Objekt gern noch von aussen arbeiten moechte. Die Erweiterung auf Variadic Templates mittelsparam...ist erstmal icht wichtig, da Visual Studio 11 Beta die nicht unterstuetzt.PS: Performance ist nicht so wichtig, da
stopundstartnur jeweils einmal aufgerufen werden sollen.
-
gleich nach dem Konstruktor der Destruktor aufgerufen
Ich Dummbatzen. Natuerlich muss ich auch ein Objekt benennen, also
service2 s(...), da sonst nur ein temporaeres Objekt erzeugt und gleich wieder vernichtet wird. Mittlerweile funktioniert alles wie gewuenscht, wobei ich den Ansatz mitstd::functionnoch untersuchen werde. Danke!