web-dev-qa-db-fra.com

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 fil. Dans certains 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

La norme C++ ne traite pas du tout des é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 des moyens d'attendre pour, les expédier et les utiliser.

En dehors de cela, vous voudrez peut-être regarder Boost.Signals2 .

10
Max Lybbert

Souvent, les files d'attente d'événements sont implémentées sous la forme modèle de conception de commande :

Dans la programmation orientée objet, le modèle de commande est un modèle de conception dans lequel un objet est utilisé pour représenter et encapsuler toutes les informations nécessaires pour appeler une méthode ultérieurement. Ces informations incluent le nom de la méthode, l'objet propriétaire de la méthode et les valeurs des paramètres de la méthode.

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

Voici un exemple minimaliste comment implémenter une file d'attente d'événements entre plusieurs threads producteurs et plusieurs threads consommateurs. 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);
}

Les sorties:

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

La mise en oeuvre:

#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_;
};
12
Maxim Egorushkin

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 contient un exemple de code qui montre comment l'utiliser.

Si vous devez suivre les événements (par exemple, les frappes) et les traiter de manière FIFO (premier entré, premier sorti)), vous devrez alors utiliser ou type de système de file d'attente d'événements multithread, comme suggéré dans certaines des autres réponses.

7
Emile Cormier

C++ n'a pas de support intégré pour les événements. Vous devez implémenter une sorte de file d'attente de tâches thread-safe. Votre thread principal de traitement des messages retirerait continuellement des éléments 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;
 }

D'autres threads peuvent Post un message vers une fenêtre, qui sera alors gérée par ce thread.

Cela utilise C plutôt que C++, mais cela illustre l'approche.

5
Andrew Shepherd