Обработка событий в С++
От: Алексндр Клюев  
Дата: 04.02.03 06:58
Оценка: 161 (9) +1
Статья :
Обработка событий в С++
Автор(ы): Алексндр Клюев
Дата: 29.01.2003


Авторы :
Алексндр Клюев

Аннотация :
Так уж исторически сложилось, что в языке С++ нет событий. Событием (event) является исходящий вызов (программисты на VB хорошо знакомы с ними) и в С++ их действительно нет. Иногда события путают с сообщениями (message), но это не верно. Сообщение это прямой вызов: например windows вызывает оконную процедуру для передачи собщения окну. Объект (система) вызывает функцию обькта(окна). Вызов происходит от объекта к объекту. В отличии от сообщения событие имеет другую механику. Объект инициирует событие и вызываются все объекты-обработчики. Т.е. от одного объекта к нескольким. Причем объект инициатор события может ничего не «знать» об его обработчиках, поэтому событие называют исходящим вызовом.

Раз уж в С++ события на уровне языка не поддерживаются, значит стоит организовать их на уровне библиотеки. Здесь приведена реализация такой библиотеки. В ней есть два класса signal и slot.
Не всегда корректный код
От: yaroslav_v http://yaroslav-v.chat.ru
Дата: 10.02.03 14:11
Оценка:
Вы приводите указатель на функцию-член класса клиента
к уназателю на функцию из конкрентного класса (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.

Возможно в свете .Net на такие проблемы можно не смотреть. ;-)
делов-то
От: Евгений Коробко  
Дата: 10.02.03 09:13
Оценка:
На самом деле ничего принципиально нового тут нет. Обычный callback. Чем это принципиально лучше чем ConnectionPoints из COM?
Евгений Коробко
Хмм...
От: null  
Дата: 06.02.03 10:10
Оценка:
что-то не очень...

в Boost есть реализация подобного
интересна тем, что:

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

так что, конечно неплохо, но та реализация, IMHO, лучше...
Сильно !
От: Аноним  
Дата: 05.02.03 10:40
Оценка:
Не хуже, чем в QT ихние эвенты.И не надо макросов гопницких
Отзыв
От: VCoder http://wxbar.sf.net
Дата: 05.02.03 06:58
Оценка:
Просто и элегантно.
Супер!!!
С уважением,
Дмитрий.
Re: Хмм...
От: Alex4x  
Дата: 20.02.03 09:55
Оценка:
А можно ссылочку на этот Boost ?
Или это http://www.boost.org/libs/thread/doc/index.html ???

Спасибо ....
Re: Не всегда корректный код
От: Kluev  
Дата: 10.02.03 14:47
Оценка:
Я в свое время пробовал использовать указатели на функцию в качестве аргументов шаблонов, но оказалось что в VC++ с этим совсем тухло. Получал internal compiler error и прочие странности.

Проблемму с разнородностью указателей можно решить если сделать класс slot шаблонным т.е:

template <class Arg>
struct slot_base { // базовый интерфейсный класс
virtual void event_handle( Arg ) = 0;
}

template <class Owner, class Arg>
struct slot : slot_base {
typedef void (Owner::*Hfn)(Arg);
Owner *_owner;
Hfn *_hfn;

void event_handle( Arg a ) {
(_owner->*_hfn)(a);
}
}

Работать будет безотказно, но тогда прийдется отказатся от удобств ;)
Так что, если найдете универсальную и удобную реализацию будет супер
Re: делов-то
От: null  
Дата: 10.02.03 10:09
Оценка:
суть в переносимости :-)
Re: Хмм...
От: Kluev  
Дата: 06.02.03 17:08
Оценка:
А разрыв соединеия при уничтожении одного из обьектов там происхолдит?
Re: Хмм...
От: limax Эстония http://mem.ee
Дата: 03.04.03 08:55
Оценка:
http://www.boost.org/doc/html/signals.html
Have fun: Win+M, Ctrl+A, Enter
Re: Не всегда корректный код
От: Kluev  
Дата: 10.02.03 14:54
Оценка:
Опс! забыл аргумент шаблона указать
template <class Owner, class Arg>
struct slot : slot_base<Arg> { .....
Re: Хмм...
От: null  
Дата: 07.02.03 13:58
Оценка:
не совсем корректный вопрос...
там несколько другой подход...

есть объект signal;

есть объект-обработчик, т.е. то, что мы коннектим к signal
в реализации(внутри) он, конечно, signal создает объект slot, но это происходит прозрачно для пользователя,
т.е. для Вас есть только signal и объект-обработчик (и никаких slot)

объекту signal передается объект-обработчик
но даже если передается адрес функции это соединение тоже можно отключить, а именно:
при создание связи "signal"-"slot" возвращает объект connection, при этом есть два варианта: соединение живет до уничтожения объекта connection или дольше, рекоммендую взглянуть в реализацию boost::signals:

www.boost.org

пример:

// hello : скажем — функция void hello();

signal<void ()> sig;

{
scoped_connection c = sig.connect(hello);
}// соединение убъется при выходе из блока

{
connection c = sig.connect(hello);
}// соединение не убъется при выходе из блока

// присваивать к connection, конечно, не обязательно, но в этом случае
// разрыв произойдет только при уничтожениии sig
// при этом объект-обработчик может быть назначен различным сигналам
Re: Хмм...
От: Kluev  
Дата: 07.02.03 17:49
Оценка:
Очевидно что при таком подходе связь будет разорвана только при уничтожении источника события(сервера) если обработчик события (клиент) будет уничтожен раньше програма долго не проживет. В реальной жизни клиент как правило уничтожается раньше сервера. Пример документ(сервер) клиент (вид) сначала уничтожаются виды потом документ. Поэтому важно разрывать связь в любом случае, что здесь и сделано (см. код ;) )
А иначе прийдется ручками разрывать :)
Re: Хмм...
От: null  
Дата: 08.02.03 19:30
Оценка:
согласен...
но событий, зачастую, бывает несколько и обработчик должен реагировать них...
реализация boost позволяет это сделать... ;)

для описанного Вами случая есть и класс slot(ведь я не сказал что его там нет, просто можно им не пользоваться):

CLASS_HANDLER ch;
slot s(ch);
// или так: slot s(&ch);

есть даже класс слежения за соединениями, для своевременного разрыва их, trackable

я бы назвал достоинства Вашей реализации, такие как: легкая и быстрая :-)
Re: Хмм...
От: Kluev  
Дата: 09.02.03 06:51
Оценка:
Эта реализация тоже позволяет. Добавляется несколько несколько slot-ов в класс. Можно для каждого слота по функции обработчику или одну на всех (если тип аргумента сиглала совпадает)
class MyClass {
slot onSignal0, onSignaд1, .... // до бесконечности
}
Re: Хмм...
От: null  
Дата: 10.02.03 10:05
Оценка:
но как раз дело в том, что для каждого сигнала требуется свой обработчик, т.е. получается
придется для варианта сдвумя сигналами и одним обработчиком писать след. конструкцию:

signal1->handler1-\
>->signal3->handler3
signal2->handler2-/

т.к. хендлеры выстраиваются в двусвязный список, и передачя одного и тогоже хендлера потянет за собой другие в цепочке, т.е. цепочки двух сигналов склеятся :-)

IMHO так, если есть другой способ поправте меня...
т.е. учитывая что оба сигнала не знают друг о друге, а хендлеры передаються им...
Re: Хмм...
От: Kluev  
Дата: 10.02.03 10:16
Оценка:
Хм... Странная конструкция. Я бы не додумался ;)
Достаточно в два разных slota передать один и тот же указатель на функцию

struct MyClass {
slot s0, s1;

void onEvent() { // bla-bla-bla }

void connect( signal<void> &sig0, signal<void> &sig1 ) {
s0.init( sig0, onEvent, this ); // сигналы разные
s1.init( sig1, onEvent, this ); // обработчик один
}
};
Re[2]: Хмм...
От: MaximE Великобритания  
Дата: 22.06.03 08:04
Оценка:
Здравствуйте, Kluev, Вы писали:

K>А разрыв соединеия при уничтожении одного из обьектов там происхолдит?


Происходит. Смотреть документацию к boost::signals topic "Connection Management".
Re: Не всегда корректный код
От: Андрей Тарасевич Беларусь  
Дата: 22.06.03 17:02
Оценка:
Здравствуйте, yaroslav_v, Вы писали:

_>Вы приводите указатель на функцию-член класса клиента

_>к уназателю на функцию из конкрентного класса (slot::Thunk),
_>это для некоторых классов может быть невозможно,
_>ошибка компилятора, что-то типа "указатели имеют разную природу",
_>наблюдатась для WTL проекта, я в свое время не стал
_>углубляться, удалось обойтись

Ошибку на такое приведение может выдать только некорректный компилятор. Такое приведение всегда доложно прекрасно выполняться при помощи 'reinterpret_cast'.

_>В C++ указатели на функцию-член не всегда просто

_>адрес функции, нельзя приводить указатель на функцию одного
_>класса к указателю на функцию другого.

Указатель на функцию одного класса в С++ можно приводить к указателю на функцию любого другого класса при помощи 'reinterpret_cast'. Портабельное применение такого приведения типа ограничивается толкро тем, что рано или поздно придется сделать обратное преобразование и, согласно спецификации языка, при этом получится исходное значение. Именно на этом принципе построен данный код. Прямое преобразование делается в при инициализации слота, обратное — при вызове функции.
Best regards,
Андрей Тарасевич
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.