Quelle est la bonne façon de gérer les événements en C++?

J'ai une application qui doit répondre à certains événements de la manière suivante:

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

Ce serait en cours d'exécution est un thread. Dans d'autres threads, les opérations créeraient et déclencheraient les événements.

Comment puis-je obtenir ces événements pour atteindre la méthode/classe ci-dessus? Quelle est la stratégie ou l'architecture appropriée pour implémenter la gestion des événements en C++?

21
demandé sur Sotirios Delimanolis 2012-03-15 02:41:48

4 réponses

La norme C++ ne traite pas du tout les événements. Habituellement, cependant, si vous avez besoin d'événements, vous travaillez dans un cadre qui les fournit (SDL , Windows, Qt, GNOME, etc.) et les moyens d'attendre, d'expédition et de les utiliser.

En dehors de cela, vous pouvez regarder Boost.Signals2 .

9
répondu Max Lybbert 2012-03-14 22:51:09

Souvent, les files d'attente d'événements sont implémentées comme Modèle de conception de commande :

En programmation orientée objet, le modèle de commande est un design modèle dans lequel un objet est utilisé pour représenter et encapsuler tout les informations nécessaires pour appeler une méthode à une date ultérieure. Ce les informations incluent le nom de la méthode, l'objet qui possède la méthode et les valeurs pour les paramètres de méthode.

En C++, l'objet qui possède la méthode et les valeurs de la méthode parameters est un foncteur nullaire (c'est-à-dire un foncteur qui ne prend aucun argument). Il peut être créé en utilisant boost::bind() ou C++11 lambdas et enveloppé dans boost::function.

Voici un exemple minimaliste d'implémentation d'une file d'attente d'événements entre plusieurs threads producteurs et consommateurs. Utilisation:

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);
}

Sorties:

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

Mise en œuvre:

#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_;
};
11
répondu Maxim Egorushkin 2012-03-17 21:15:36

C++11 et Boost ont variables de condition . Ils sont un moyen pour un thread de débloquer un autre qui attend qu'un événement se produise. Le lien ci-dessus vous amène à la documentation de boost::condition_variable, et a un exemple de code qui montre comment l'utiliser.

Si vous avez besoin de garder une trace des événements (par exemple, des frappes) et que vous devez les traiter de manière FIFO (premier entré, premier sorti), vous devrez utiliser ou créer un système de file d'attente d'événements multi-thread, comme suggéré dans certains des d'autres réponses.

6
répondu Emile Cormier 2012-03-14 23:04:49

C++ ne prend pas en charge les événements. Vous devrez implémenter une sorte de file d'attente de tâches thread-safe. Votre thread principal de traitement des messages obtiendrait continuellement des éléments hors de cette file d'attente et les traiterait.

Un bon exemple de ceci est la pompe de message Win32 standard qui Pilote les applications windows:

 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;
 }

Les autres threads peuvent Post un message vers une fenêtre, qui sera ensuite géré par ce thread.

Cela utilise C plutôt que c++, mais il illustre le approche.

5
répondu Andrew Shepherd 2012-03-14 22:54:42