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

前端 未结 4 585
借酒劲吻你
借酒劲吻你 2020-11-30 06:40

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

void someMethodWithinSomeClass() {
    while (true) {
        wait for         


        
相关标签:
4条回答
  • 2020-11-30 07:17

    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.

    0 讨论(0)
  • 2020-11-30 07:20

    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.

    0 讨论(0)
  • 2020-11-30 07:27

    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_;
    };
    
    0 讨论(0)
  • 2020-11-30 07:27

    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.

    0 讨论(0)
提交回复
热议问题