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

Автор: Алексндр Клюев
Pragmaworks group
Опубликовано: 29.01.2003
Версия текста: 1.0.1

Введение
Пример
Краткое описание классов
Исходный код

Демонстрационный проект - events.zip

Введение

Так уж исторически сложилось, что в языке С++ нет событий. Событием (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_


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.