Так уж исторически сложилось, что в языке С++ нет событий. Событием (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();};
Вы приводите указатель на функцию-член класса клиента к указателю на функцию из конкрентного класса (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 ихние эвенты. И не надо макросов гопницких