What is the proper way of doing event handling in C++? What is the proper way of doing event handling in C++? multithreading multithreading

What is the proper way of doing event handling in C++?


Often, event queues are implemented as command design pattern:

In object-oriented programming, the command pattern is a design pattern in which an object is used to represent and encapsulate all the information needed to call a method at a later time. This information includes the method name, the object that owns the method and values for the method parameters.

In C++, the object that own the method and values for the method parameters is a nullary functor (i.e. a functor that takes no arguments). It can be created using boost::bind() or C++11 lambdas and wrapped into boost::function.

Here is a minimalist example how to implement an event queue between multiple producer and multiple consumer threads. Usage:

void consumer_thread_function(EventQueue::Ptr event_queue)try {    for(;;) {        EventQueue::Event event(event_queue->consume()); // get a new event         event(); // and invoke it    }}catch(EventQueue::Stopped&) {}void some_work(int n) {    std::cout << "thread " << boost::this_thread::get_id() << " : " << n << '\n';    boost::this_thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(500));}int main(){    some_work(1);    // create an event queue that can be shared between multiple produces and multiple consumers    EventQueue::Ptr queue(new EventQueue);    // create two worker thread and pass them a pointer to queue    boost::thread worker_thread_1(consumer_thread_function, queue);    boost::thread worker_thread_2(consumer_thread_function, queue);    // tell the worker threads to do something    queue->produce(boost::bind(some_work, 2));    queue->produce(boost::bind(some_work, 3));    queue->produce(boost::bind(some_work, 4));    // tell the queue to stop    queue->stop(true);    // wait till the workers thread stopped    worker_thread_2.join();    worker_thread_1.join();    some_work(5);}

Outputs:

./testthread 0xa08030 : 1thread 0xa08d40 : 2thread 0xa08fc0 : 3thread 0xa08d40 : 4thread 0xa08030 : 5

Implementation:

#include <boost/function.hpp>#include <boost/thread/thread.hpp>#include <boost/thread/condition.hpp>#include <boost/thread/mutex.hpp>#include <boost/smart_ptr/intrusive_ptr.hpp>#include <boost/smart_ptr/detail/atomic_count.hpp>#include <iostream>class EventQueue{public:    typedef boost::intrusive_ptr<EventQueue> Ptr;    typedef boost::function<void()> Event; // nullary functor    struct Stopped {};    EventQueue()        : state_(STATE_READY)        , ref_count_(0)    {}    void produce(Event event) {        boost::mutex::scoped_lock lock(mtx_);        assert(STATE_READY == state_);        q_.push_back(event);        cnd_.notify_one();    }    Event consume() {        boost::mutex::scoped_lock lock(mtx_);        while(STATE_READY == state_ && q_.empty())            cnd_.wait(lock);        if(!q_.empty()) {            Event event(q_.front());            q_.pop_front();            return event;        }        // The queue has been stopped. Notify the waiting thread blocked in        // EventQueue::stop(true) (if any) that the queue is empty now.        cnd_.notify_all();        throw Stopped();    }    void stop(bool wait_completion) {        boost::mutex::scoped_lock lock(mtx_);        state_ = STATE_STOPPED;        cnd_.notify_all();        if(wait_completion) {            // Wait till all events have been consumed.            while(!q_.empty())                cnd_.wait(lock);        }        else {            // Cancel all pending events.            q_.clear();        }    }private:    // Disable construction on the stack. Because the event queue can be shared between multiple    // producers and multiple consumers it must not be destroyed before the last reference to it    // is released. This is best done through using a thread-safe smart pointer with shared    // ownership semantics. Hence EventQueue must be allocated on the heap and held through    // smart pointer EventQueue::Ptr.    ~EventQueue() {        this->stop(false);    }    friend void intrusive_ptr_add_ref(EventQueue* p) {        ++p->ref_count_;    }    friend void intrusive_ptr_release(EventQueue* p) {        if(!--p->ref_count_)            delete p;    }    enum State {        STATE_READY,        STATE_STOPPED,    };    typedef std::list<Event> Queue;    boost::mutex mtx_;    boost::condition_variable cnd_;    Queue q_;    State state_;    boost::detail::atomic_count ref_count_;};


The C++ Standard doesn't address events at all. Usually, however, if you need events you are working within a framework that provides them (SDL, Windows, Qt, GNOME, etc.) and ways to wait for, dispatch and use them.

Aside from that, you may want to look at Boost.Signals2.


C++11 and Boost have condition variables. They are a means for a thread to unblock another one that is waiting for some event to occur. The link above brings you to the documentation for std::condition_variable, and has a code sample that shows how to use it.

If you need to keep track of events (say, keystrokes) and need to process them in a FIFO (first-in first-out) manner, then you'll have to use or make some kind of multi-threaded event queuing system, as suggested in some of the other answers. Condition variables can be used as building blocks to write your own producer/consumer queue, if you choose not to use an existing implementation.