Спасибо, что скачали книгу в бесплатной электронной библиотеке Royallib.ru
   Все книги автора
   Эта же книга в других форматах
 
   Приятного чтения!
 
 
 

Обработка событий в С++

Александр Клюев


Александр Клюев
Обработка событий в С++

Введение

   Так уж исторически сложилось, что в языке С++ нет событий. Событием (event) является исходящий вызов (программисты на VB хорошо знакомы с ними) и в С++ их действительно нет. Иногда события путают с сообщениями (message), но это не верно. Сообщение это прямой вызов: например windows вызывает оконную процедуру для передачи собщения окну. Объект (система) вызывает функцию обькта(окна). Вызов происходит от объекта к объекту. В отличии от сообщения событие имеет другую механику. Объект инициирует событие и вызываются все объекты-обработчики. Т.е. от одного объекта к нескольким. Причем объект инициатор события может ничего не «знать» об его обработчиках, поэтому событие называют исходящим вызовом.
   Раз уж в С++ события на уровне языка не поддерживаются, значит стоит организовать их на уровне библиотеки. Здесь приведена реализация такой библиотеки. В ней есть два класса signal и slot.
   Итак, чтобы сделать какой нибудь класс источником события поместите в него переменную типа signal:
   struct EventRaiser { // источник события
    signal<void> someEvent; // void – тип аргумента события
   };
   А чтобы сделать класс обработчиком поместите в него переменную типа slot, функцию обработчик и свяжите slot с обработчиком:
   struct EventHandler { // обработчик события
    slot someHandler; // переходник
    void onEvent(void) {
     // функция обработчик события
     printf("event handled");
    }
    void connect (EventRaiser& er) {
     someHandler.init(er.someEvent, onEvent, this); // установим связь события с обработчиком
    }
   };
 
   Так как эти объекты являются частью своих хозяев, не нужно заботится о времени жизни связи. Ее разрыв произойдет во время разрушения одного из них. Событие же инициируется вызвовом метода signal::raise:
   struct EventRaiser { // источник события
    signal<void> someEvent; // void – тип аргумента события
    void someFunc() {
     someEvent.raise(); // инициация события
    }
   };

Пример

   В примере создаются два класса обработчик и инициатор события, устанавливается связь между ними и иллюстрируется обработка события в нескольких объектах одновременно:
   #include "stdafx.h"
   #include "sigslot.h"
 
   struct EventRaiser { // источник события
    signal<const char*> event; // const char* – тип аргумента. может быть void
    void raise(const char *eventName) {
     printf("raising %s event\n", eventName);
     event.raise(eventName);
    }
   } g_Raiser; // глобальный объект
 
   struct EventHandler { // обработчик события
    const char *color;
    slot handler; // переходник
    void onEvent(const char *eventName) { // обработчик события
     printf("\t%s event handled in %s object\n", eventName, color);
    }
    EventHandler(const char *clr): color(clr) {
     handler.init(g_Raiser.event, onEvent, this); // установим связь
    }
   };
 
   int main(int argc, _TCHAR* argv[]) {
    EventHandler red("Red");
    g_Raiser.raise("Small"); // событие обработается в red
    {
     {
      EventHandler blue("Blue");
      g_Raiser.raise("Big"); // событие обработается в red и blue
     }
     EventHandler green("Green");
     g_Raiser.raise("Medium"); // событие обработается в red и green.
     // объект blue уничтожен, связь разорвана
    }
    return 0;
   }

Краткое описание классов

   signal – cобытие (детали реализации опущены)
   template <class Arg> // Arg – тип аргумента функции обработчика
   class signal {
   public:
    // Инициировать событие
    void raise(
     Arg arg // Арумент arg будет передан в обработчики события
    );
   };
   slot – переходник для обработки события в классе-обработчике (детали реализации опущены)
   class slot {
   public:
    // установить связь с событием и обработчиком
    template <
     class Owner, // класс-обработчик
     class Arg // Тип аргумента события.
    >
    void init(
     signal<Arg>&sig, // событие
     void (Owner::*mpfn)(Arg), // функция обработчик
     Owner *This // обьект обработчик
    );
    // установить связь с событием и обработчиком для случая signal<void>
    template <
     class Owner // класс-обработчик
    >
    void init(
     signal<void>&sig, // событие
     void (Owner::*mpfn)(), // функция обработчик
     Owner *This // обьект обработчик
    );
    // разорвать связь
    void clear();
   };

Исходный код

   Весь код находится в файле sigslot.h
   #ifndef _SIGSLOT_h_
   #define _SIGSLOT_h_
   // sigslot.h – autor Kluev Alexander kluev@pragmaworks.com
 
   template <class Arg> class signal;
 
   class slot {
    friend class signal_base;
    slot *_prev;
    slot *_next;
    struct Thunk {};
    typedef void (Thunk::*Func)();
    Thunk *_trg;
    Func _mfn;
   public:
    slot(): _trg(0), _mfn(0), _prev(0), _next(0) {}
    ~slot() {clear();}
   public:
    void clear() {
     if (_next) _next->_prev = _prev;
     if (_prev) _prev->_next = _next;
     _prev = _next = 0;
    }
    template <class Owner, class Arg>
    void init(signal<Arg>&sig, void (Owner::*mpfn)(Arg), Owner *This) {
     clear();
     _trg = (Thunk*)This;
     _mfn = (Func)mpfn;
      sig._add(*this);
    }
    template <class Owner>
    void init(signal<void>&sig, void (Owner::*mpfn)(), Owner *This) {
     clear();
     _trg = (Thunk*)This;
     _mfn = (Func)mpfn; sig._add(*this);
    }
   private:
    template <class Arg>
    void _call(Arg a) {
     typedef void (Thunk::*XFunc)(Arg);
     XFunc f = (XFunc)_mfn;
     (_trg->*f)(a);
    }
    void _call() {
     (_trg->*_mfn)();
    }
   };
 
   class signal_base {
   protected:
    friend class slot;
    slot _head;
    void _add(slot&s) {
     s._prev =&_head;
     s._next = _head._next;
     if (_head._next) _head._next->_prev =&s;
     _head._next =&s;
    }
    template <class Arg>
    void _raise(Arg a) {
     slot *p = _head._next;
     while (p) {
      p->_call(a);
      p = p->_next;
     }
    }
    void _raise() {
     slot *p = _head._next;
     while (p) {
      p->_call();
      p = p->_next;
     }
    }
   public:
    ~signal_base() {
     clear();
    }
   public:
    void clear() {
     while (_head._next) _head._next->clear();
    }
   };
 
   template <class Arg>
   class signal: public signal_base {
   public:
    void raise(Arg);
   };
 
   typedef void VOID;
 
   template <>
   void signal<VOID>::raise() {
    signal_base::_raise();
   }
 
   template <class Arg>
   void signal<Arg>::raise(Arg a) {
    signal_base::_raise(a);
   }
 
    #endif // _SIGSLOT_h_  

Комментарии:  

Не всегда корректный код
   Вы приводите указатель на функцию-член класса клиента к указателю на функцию из конкрентного класса (slot::Thunk), это для некоторых классов может быть невозможно, ошибка компилятора, что-то типа "указатели имеют разную природу", наблюдатась для WTL проекта, я в свое время не стал углубляться, удалось обойтись.
   Кстати эта проблема нашла отражение в FLTK (библиотека типа WTL/Qt, etc., http://www.fltk.org)/– там все события вызывают статические функции с параметром-указателем this:
   static void static_cb(void* v) {
    handler* h=(handler*)v;
    h->member();
   }
   В C++ указатели на функцию-член не всегда просто адрес функции, нельзя приводить указатель на функцию одного класса к указателю на функцию другого. Однако возможно есть один способ:
   template<class TyClass::*f)()>
   void call(TyClass* p_this) {(
    p_this->*f)();
   }
   т.е. сделать обычную функцию с параметром this, параметризованную функцией-членом, а на эту обычную функцию уже хранить указатель.
   class foo {
    public: void f() {}
   };
   typedef void (*call_f_type)(void*);
   call_f_type call_f=(call_f_type)(call<&foo::f>);
   а теперь
   foo obj;
   call_f(&obj);
   Проблема здесь в том, что VC++ может не понять, что (call<&foo::f>) означает, что надо сгенерировать функцию и взять указатель на нее, ну и конечно как изменить Ваш пакет – как известно удобство важнее всего.
   Интересно как это сделано в boost.
yaroslav_v 10.2.2003 17:11
делов-то
   На самом деле ничего принципиально нового тут нет. Обычный callback. Чем это принципиально лучше чем ConnectionPoints из COM?
Евгений Коробко 10.2.2003 12:13
Хмм…
   что-то не очень…
   в Boost есть реализация подобного интересна тем, что:
   • также является шаблонным классом
   • слот может реагировать на несколько сигналов
   • сигнал вызывает объект с перегруженным оператором (), т.е. не обязателен отдельный объект типа слот…
   • можно передавать не только объект-слот, но и просто указатель на функцию и работать будет с тем же успехом…
   так что, конечно неплохо, но та реализация, IMHO, лучше…
null 6.2.2003 13:10
   Не хуже, чем в QT ихние эвенты. И не надо макросов гопницких
Huang Bai Wei 5.2.2003 13:40
 

   Спасибо, что скачали книгу в бесплатной электронной библиотеке Royallib.ru
   Оставить отзыв о книге
   Все книги автора