Александр Клюев
Обработка событий в С++
Введение
Так уж исторически сложилось, что в языке С++ нет событий. Событием (event) является исходящий вызов (программисты на VB хорошо знакомы с ними) и в С++ их действительно нет. Иногда события путают с сообщениями (message), но это не верно. Сообщение это прямой вызов: например windows вызывает оконную процедуру для передачи собщения окну. Объект (система) вызывает функцию обькта(окна). Вызов происходит от объекта к объекту. В отличии от сообщения событие имеет другую механику. Объект инициирует событие и вызываются все объекты-обработчики. Т.е. от одного объекта к нескольким. Причем объект инициатор события может ничего не «знать» об его обработчиках, поэтому событие называют исходящим вызовом.
Раз уж в С++ события на уровне языка не поддерживаются, значит стоит организовать их на уровне библиотеки. Здесь приведена реализация такой библиотеки. В ней есть два класса signal и slot.
Итак, чтобы сделать какой нибудь класс источником события поместите в него переменную типа signaclass="underline"
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); // установим связь события с обработчиком
}
};
Так как эти объекты являются частью своих хозяев, не нужно заботится о времени жизни связи. Ее разрыв произойдет во время разрушения одного из них. Событие же инициируется вызвовом метода signaclass="underline" :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);
}