I am wanting to write library code that will allow a user to create an "event" which is associated with a thread, a class instance, and a member function of that class. The user can then "post" data (generalised number of arguments) - which results in the class instance's member function being called in the context of the associated thread. For example (pseudo code): event { thread* thr; class &that; mem_fun* fun; void post(); void post(a1); void post(a1, a2); void post(a1, a2, a3, ...etc); } mem_fun* fun stores a member function pointer which is of the form void class::fun(...0, 1 or more arguments...) Here is some example code which uses boost::bind to bind arguments to member functions and posts them to be called on another thread. However, it does not go far enough. #include <boost/function.hpp>
#include <boost/bind.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/thread/thread.hpp> #include <boost/thread/mutex.hpp> #include <boost/thread/condition.hpp> #include <list> #include <stdio.h>
// cross-thread event queue. multiple threads can post jobs, one or many threads can execute // jobs. class EventQueue { public: typedef boost::function<void()> Job;
// puts a jon into the queue void post(Job job) { boost::mutex::scoped_lock lock(mtx_); jobs_.push_back(job); cnd_.notify_one(); }
// pulls one job from the queue, returns false when stopped bool pull(Job* job) { boost::mutex::scoped_lock lock(mtx_); for(;;) { // handle spurious wake-ups while(!stop_ && jobs_.empty()) cnd_.wait(lock); if(!jobs_.empty() && 2 != stop_) { job->swap(jobs_.front()); // move efficiently, avoiding *job = jobs.front() jobs_.pop_front(); return true; } else if(stop_) { return false; } } }
// make pull() return false void stop(bool cancel_jobs) { boost::mutex::scoped_lock lock(mtx_); stop_ = 1 + cancel_jobs; // 1 - complete jobs, 2 - cancel jobs cnd_.notify_all(); }
EventQueue() : stop_() {} ~EventQueue() { this->stop(true); }
private: boost::mutex mtx_; boost::condition cnd_; typedef std::list<Job> Jobs; Jobs jobs_; int stop_; };
struct JobX // micro-oprimization, embed the reference counter into the job to avoid an extra memory // allocation by boost::shared_ptr ctor : boost::enable_shared_from_this<JobX> { void foo() { printf("%p foo()\n", this); }
void bar(int a) { printf("%p bar(%d)\n", this, a); } };
void anotherThread(EventQueue* queue) { EventQueue::Job job; // wait and execute jobs till stopped while(queue->pull(&job)) job(); // execute the job }
int main() { EventQueue queue; // start another thread and pass an argument boost::thread another_thread(boost::bind(anotherThread, &queue));
// post jobs, allocate in this thread, deallocate in the other boost::shared_ptr<JobX> job_0(new JobX); queue.post(boost::bind(&JobX::foo, job_0)); // post several jobs to the same object, deallocated when no longer in use boost::shared_ptr<JobX> job_1(new JobX); queue.post(boost::bind(&JobX::foo, job_1)); queue.post(boost::bind(&JobX::bar, job_1, 1)); // pass an extra arg 1 queue.post(boost::bind(&JobX::bar, job_1, 2)); // pass an extra arg 2
// stop the queue and let it complete all jobs queue.stop(false); another_thread.join(); }
And this is where I fall down. I am able to bind arguments to JobX::bar above, at the point at which I create my boost::function<void()>, but if I want to bind _1, _2 etc arguments for calling later, I'm not sure what to do. So my question is, how should I form class Event such that I can store member functions of the form void T::fn(), void T::fn(a1), void T::fn(a1, a2, etc...), and then allow a generalised form of post(), post(a1), post(a1, a2, etc...) to bind the arguments given in post to the callback and post it to the queue? I have some code below which clearly won't compile, but should illustrate to some degree what I'm looking for. template <typename T> class Event { EventQueue &queue_; // the event queue which will process the job boost::shared_ptr<T> that_; // the object whose mem_fun the event queue will call typedef boost::function<void (T*, void) > CB_0 ; // used for callbacks which are of the form void T::fn(); typedef boost::function<void (T*, _1) > CB_1 ; // used for callbacks which are of the form void T::fn(1 arg); typedef boost::function<void (T*, _1, _2) > CB_2 ; // used for callbacks which are of the form void T::fn(1 arg); CB_0 cb_0; CB_1 cb_1; CB_2 cb_2; public: Event(boost::shared_ptr<T> &that, CB &cb, EventQueue &queue) : that_(that), cb_(cb), queue_(queue) {} void post() { queue_.post(boost::bind(&cb_0, that)); template <typename arg_1> void post(arg_1 &arg1) { queue_.post(boost::bind(&cb_1, that)); template <typename arg_1, typename arg_2> void post(arg_1 &arg1, arg_2 &arg2) { queue_.post(boost::bind(&cb_2, that)); // TODO: args should be shared_ptr }; TIA Steve