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


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

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

Раз уж в С++ события на уровне языка не поддерживаются, значит стоит организовать их на уровне библиотеки. Здесь приведена реализация такой библиотеки. В ней есть два класса signal и slot.
Отзыв
От: VCoder http://wxbar.sf.net
Дата: 05.02.03 06:58
Оценка:
Просто и элегантно.
Супер!!!
С уважением,
Дмитрий.
Сильно !
От: Аноним  
Дата: 05.02.03 10:40
Оценка:
Не хуже, чем в QT ихние эвенты.И не надо макросов гопницких
Хмм...
От: null  
Дата: 06.02.03 10:10
Оценка:
что-то не очень...

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

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

так что, конечно неплохо, но та реализация, IMHO, лучше...
Re: Хмм...
От: Kluev  
Дата: 06.02.03 17:08
Оценка:
А разрыв соединеия при уничтожении одного из обьектов там происхолдит?
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, .... // до бесконечности
}
делов-то
От: Евгений Коробко  
Дата: 10.02.03 09:13
Оценка:
На самом деле ничего принципиально нового тут нет. Обычный callback. Чем это принципиально лучше чем ConnectionPoints из COM?
Евгений Коробко
Re: Хмм...
От: null  
Дата: 10.02.03 10:05
Оценка:
но как раз дело в том, что для каждого сигнала требуется свой обработчик, т.е. получается
придется для варианта сдвумя сигналами и одним обработчиком писать след. конструкцию:

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

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

IMHO так, если есть другой способ поправте меня...
т.е. учитывая что оба сигнала не знают друг о друге, а хендлеры передаються им...
Re: делов-то
От: null  
Дата: 10.02.03 10:09
Оценка:
суть в переносимости :-)
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 ); // обработчик один
}
};
Не всегда корректный код
От: 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 на такие проблемы можно не смотреть. ;-)
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: Не всегда корректный код
От: Kluev  
Дата: 10.02.03 14:54
Оценка:
Опс! забыл аргумент шаблона указать
template <class Owner, class Arg>
struct slot : slot_base<Arg> { .....
Re: Хмм...
От: Alex4x  
Дата: 20.02.03 09:55
Оценка:
А можно ссылочку на этот Boost ?
Или это http://www.boost.org/libs/thread/doc/index.html ???

Спасибо ....
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[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,
Андрей Тарасевич
Re[2]: Не всегда корректный код
От: Аноним  
Дата: 22.06.03 19:23
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:

<...>

АТ>Указатель на функцию одного класса в С++ можно приводить к указателю на функцию любого другого класса при помощи 'reinterpret_cast'.



Т.е. это проблемы MSVC, что у него указатель на функцию-член бывает всех размеров — от 4 до 12, и зависит от вируальности функции и наследования ?
Re[3]: Не всегда корректный код
От: Андрей Тарасевич Беларусь  
Дата: 22.06.03 19:39
Оценка:
Здравствуйте, Аноним, Вы писали:

<...>>

АТ>>Указатель на функцию одного класса в С++ можно приводить к указателю на функцию любого другого класса при помощи 'reinterpret_cast'.


А>Т.е. это проблемы MSVC, что у него указатель на функцию-член бывает всех размеров — от 4 до 12, и зависит от вируальности функции и наследования ?


И да, и нет. Разноразмерные указатели у MSVC бывают при умолчатеьных установках проекта: модель указателя на член класса = 'Best case always'. Зайди в установки проекта и явно включи более общую модель указателя на член класса — и все станет в порядке.
Best regards,
Андрей Тарасевич
Re: Обработка событий в С++
От: Viktor Sazhaev  
Дата: 17.07.03 07:18
Оценка:
Здравствуйте.

Идея хорошая. Мне тоже приходили в голову мысли сделать что-то подобное, так что будет что

дополнить. М. б. окажется полезным.

Во-первых, Вы сохраняете и вызываете указатель на функцию-обработчик как указатель на член

структуры slot::Thunk. Это неправильно, потому что не будет работать со множественным

наследованием, также как и в классическом случае с приведением к void*. При множественном

наследовании может требоваться коррекция указателей для вызова методов.

Так что придётся параметризовать класс slot классом Owner, методы которого оттуда

вызываются, либо предпринимать усилия для коррекции указателей вручную — запонминать

смещение и потом его прибавлять/вычитать. Конечно, если не предполагается использовать

механизм вместе со множественным наследованием, то ничего такого делать не нужно. Надо

только предупредить об этом как об ограничении.

В первом случае страшного ничего нет, поскольку один и тот же объект будет использоваться

только с одинм Owner'ом. Здесь возникает единственно потребность сделать общий базовый класс

для наследования под разных Owner'ов и объявить _call() виртуальной.

Во-вторых, по поводу (кажущейся) избыточности. Вам не приходила мысль избавиться от

необходимости хранения в каждом slot'е указателя на Owner'а? Он же его содержит. Я точно не

знаю, возможно ли это, но есть соблазн попробовать вычислять смещение к члену типа slot

внутри Owner'а.

Можно ещё рассмотреть вариант, при котором созданные слоты удалять не нужно, тогда не нужен

двунаправленный список, достаточно однонаправленного. Ещё можно убрать указатели вообще,

добавив в slot указатель на signal, а в signal сохранить массив подцепленных slot'ов. Второе

нам ничего не экономит, лишь позволяет установить обратную связь, которая может пригодиться,

но в обмен на то, что невозможно будет привязать один slot к нескольким signal'ам.

Спасибо.
Re[4]: Не всегда корректный код
От: Viktor Sazhaev  
Дата: 17.07.03 07:31
Оценка:
Не так всё просто. Там же сначала кастуется к указателю на член Thunk, а потом прямо так и вызывается, как метод Thunk. Информация о типе Owner уже потеряна, так что даже для универсального представления указателей вызов теоретически может быть неверным. В любом случае это неправильно — преобразовывать указатели на члены друг в друга. Это борьба с системой типов, которая выходит боком.
Re[5]: Не всегда корректный код
От: Kluev  
Дата: 17.07.03 09:40
Оценка:
Здравствуйте, Viktor Sazhaev, Вы писали:

VS>Не так всё просто. Там же сначала кастуется к указателю на член Thunk, а потом прямо так и вызывается, как метод Thunk. Информация о типе Owner уже потеряна, так что даже для универсального представления указателей вызов теоретически может быть неверным. В любом случае это неправильно — преобразовывать указатели на члены друг в друга. Это борьба с системой типов, которая выходит боком.


Программирования без борьбы не бывает, главное чтобы эта борьба была осознанной. Я почи во всех сових проектах юзаю эту библиотеку и ни разу не прогорел. В реальной жизни в слот передается указатель и функции из одного класса. Согласитесь делать slot в одном классе, а handler в другом (даже в базовом) не есть хороший стиль программирования. Когда slot и handler находтся в одном классе проблем возникнуть не может по определению, независимо от того есть множественное наследование или нет.
Re[6]: Не всегда корректный код
От: yaroslav_v http://yaroslav-v.chat.ru
Дата: 20.07.03 00:44
Оценка:
Если не хочешь верить правильной теории, то вот 2 строчки кода, которые все могут испортить

struct base1 {};
struct base2 {};

struct EventHandler : base1,base2 {
    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, &EventHandler::onEvent, this );
    }

};


Microsoft (R) 32-bit C/C++ Standard Compiler Version 13.10.3077 for 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.
events.cpp
c:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin\sigslot.h(42)
: error C2440: 'type cast' : cannot convert from 'void (__thiscall EventHandler::* )(Arg)' to 'slot::Func'
        with
        [
            Arg=const char *
        ]
        Pointers to members have different representations; cannot cast between them
        events.cpp(32) : see reference to function template instantiation
'void slot::init<EventHandler,const char*>(signal<Arg> &,void (__thiscall EventHandler::* )(Arg),Owner *)' being compiled
        with
        [
            Arg=const char *,
            Owner=EventHandler
        ]

Заметь — это последняя версия компилятора (VS.NET2003), VC6, конечно тоже ругается.
Т.е. указатели на разные классы нельзя преобразовывать друг к другу.
Мозно включить (естественно нестандартную) опцию в MSVC, но тогда пропадет оптимизация,
а это же C++ — тут невидимая, но сильная оптимизация есть одно из основных преимуществ языка.


Да даже если бы можно было — все равно был бы вылет:

У тебя в коде указатель на функцию EventHandler::onEvent (который занимает 16 байт) хранится как указатель на
функцию Thunk::f, который занимает 4 байта — очевидно, что это некорректно.
    struct Thunk {};
    typedef void (Thunk::*Func)();

    Thunk    *_trg;
    Func    _mfn;


Естественно эта проблема решена и давно (см. http://www.boost.org, http://www.rsdn.ru/article/?cpp/delegates.xml
Автор(ы): Александр Шаргин
Дата: 19.03.2003
Делегаты в CLR удобны, типобезопасны и эффективны. Последнее время на форумах RSDN часто поднимается вопрос о том, можно ли реализовать делегаты с аналогичными свойствами, оставаясь в рамках "чистого" C++. Оказывается, это вполне возможно. В этой статье я покажу, как это сделать.
)
более того у тебя явно зашит тип указателя "Owner*", это естественно не самый лучший метод — например

    void onEvent( const char *eventName ) const


уже не будет компилиться — лечится заменой "Owner*" добавлением нового шаблонного параметра "class OwnerPtrTy" и
заменой "Owner*" на "OwnerPtrTy", это кстати позволит связывать signal не только с обычными указателями, но и со
"smart-pointer'ами", что в современных программах на C++ нужно не редко.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.