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

4

34

I have an application that needs to respond to certain events in the following manner:

void someMethodWithinSomeClass() {
    while (true) {
        wait for event;
        if (event == SomeEvent) {
            doSomething();
            continue;
        }
        if (event == SomeOtherEvent) {
            doSomethingElse();
            continue;
        }
    } 
}

This would be running is some thread. In some other threads, operations would create and fire the Events.

How do I get these Events to reach the above method/class? What is the proper strategy or architecture for implementing event handling in C++?

Pantywaist answered 14/3, 2012 at 22:41 Comment(6)
The C++ language doesn't really have native support for this sort of thing. You will need to use the API for whatever OS you're working on.Creasy
Generally modern C++ uses signals and slots (see Boost.Signals2) rather than message passing for events. The approach you're showing is already archaic, so C++ has nothing special to offer as a language to support it.Frenzy
Do some searches for BlockingQueue. The handler will block on queue get() until the event is posted to the queue.Tenantry
Do the events need to be queued? For example, lets say these events are keystrokes. While handling the event for a keystroke 'A', if keystrokes 'B' and 'C' come in, does your program need to keep track and "remember" the 'B' and 'C' keystrokes?Polarimeter
The do not need to be ordered.Pantywaist
uh ildjarn... There is no C++ standard called 'signals and slots'. Boost and QT have API's that use that name. There is on the other hand a pattern of software design called the 'observer pattern', and that simply is what Signals and slots is.Eley
T
11

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.

Telephotography answered 14/3, 2012 at 22:51 Comment(3)
Note that while Boost.Signals2 is thread-safe, it does not provide a mechanism for queuing events to be dispatched by another thread.Polarimeter
@Max Lybbert, Is this still the case? Microsoft has thisBeck
@DanielLord, the title to your piece is "Event handling in native C++," which sounds promising. But the help for __event takes away that hope: "The Microsoft-specific keyword __event can be applied to a member function declaration..."Clericalism
P
16

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:

./test
thread 0xa08030 : 1
thread 0xa08d40 : 2
thread 0xa08fc0 : 3
thread 0xa08d40 : 4
thread 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_;
};
Peale answered 14/3, 2012 at 23:31 Comment(4)
I can't use boost. What are my options for implementing event handling?Spiegel
@Spiegel Use std:: equivalents then.Peale
Sorry I couldn't edit directly, but I think you are missing a pair of { } brackets to enclose the void consumer_thread_function(EventQueue::Ptr event_queue) function (top most section of code).Aurelioaurelius
@Aurelioaurelius It is good that you could not edit, because the code is well formed. See en.cppreference.com/w/cpp/language/function-try-blockPeale
T
11

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.

Telephotography answered 14/3, 2012 at 22:51 Comment(3)
Note that while Boost.Signals2 is thread-safe, it does not provide a mechanism for queuing events to be dispatched by another thread.Polarimeter
@Max Lybbert, Is this still the case? Microsoft has thisBeck
@DanielLord, the title to your piece is "Event handling in native C++," which sounds promising. But the help for __event takes away that hope: "The Microsoft-specific keyword __event can be applied to a member function declaration..."Clericalism
P
10

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.

Polarimeter answered 14/3, 2012 at 22:59 Comment(1)
P
7

C++ does not have built-in support for events. You would have to implement some kind of thread-safe task queue. Your main message-processing thread would continually get items off this queue and process them.

A good example of this is standard Win32 message pump that drives windows applications:

 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
 {
   MSG msg;
   while(GetMessage(&msg, NULL, 0, 0) > 0)
   {
     TranslateMessage(&msg);
     DispatchMessage(&msg);
   }
   return msg.wParam;
 }

Other threads can Post a message to a window, which will then be handled by this thread.

This uses C rather than C++, but it illustrates the approach.

Ptolemaeus answered 14/3, 2012 at 22:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.