- 1
- 2
Александр Клюев Обработка событий в С++
Введение
Так уж исторически сложилось, что в языке С++ нет событий. Событием (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
- 1
- 2