Аннотация :
Так уж исторически сложилось, что в языке С++ нет событий. Событием (event) является исходящий вызов (программисты на VB хорошо знакомы с ними) и в С++ их действительно нет. Иногда события путают с сообщениями (message), но это не верно. Сообщение это прямой вызов: например windows вызывает оконную процедуру для передачи собщения окну. Объект (система) вызывает функцию обькта(окна). Вызов происходит от объекта к объекту. В отличии от сообщения событие имеет другую механику. Объект инициирует событие и вызываются все объекты-обработчики. Т.е. от одного объекта к нескольким. Причем объект инициатор события может ничего не «знать» об его обработчиках, поэтому событие называют исходящим вызовом.
Раз уж в С++ события на уровне языка не поддерживаются, значит стоит организовать их на уровне библиотеки. Здесь приведена реализация такой библиотеки. В ней есть два класса signal и slot.
Вы приводите указатель на функцию-член класса клиента
к уназателю на функцию из конкрентного класса (slot::Thunk),
это для некоторых классов может быть невозможно,
ошибка компилятора, что-то типа "указатели имеют разную природу",
наблюдатась для WTL проекта, я в свое время не стал
углубляться, удалось обойтись
Кстати эта проблема нашла отражение в FLTK (библиотека типа WTL/Qt, etc., http://www.fltk.org) — там все события вызывают статические функции
с пареметром-указателем this:
В C++ указатели на функцию-член не всегда просто
адрес функции, нельзя приводить указатель на функцию одного
класса к указателю на функцию другого.
Однако возможно есть один способ:
Проблема здесь в том, что VC++ может не понять, что
(call<&foo::f>) означает, что надо сгенерировать
функцию и взять указатель на нее, ну и конечно
как изменить Ваш пакет — как известно удобство
важнее всего.
Интересно как это сделано в boost.
Возможно в свете .Net на такие проблемы можно не смотреть. ;-)
в Boost есть реализация подобного
интересна тем, что:
также является шаблонным классом
слот может реагировать на несколько сигналов
сигнал вызывает объект с перегруженным оператором (), т.е. не обязателен отдельный объект типа слот...
можно передавать не только объект-слот, но и просто указатель на функцию и работать будет стем же успехом...
так что, конечно неплохо, но та реализация, IMHO, лучше...
Сильно !
От:
Аноним
Дата:
05.02.03 10:40
Оценка:
Не хуже, чем в QT ихние эвенты.И не надо макросов гопницких
Я в свое время пробовал использовать указатели на функцию в качестве аргументов шаблонов, но оказалось что в VC++ с этим совсем тухло. Получал internal compiler error и прочие странности.
Проблемму с разнородностью указателей можно решить если сделать класс slot шаблонным т.е:
не совсем корректный вопрос...
там несколько другой подход...
есть объект signal;
есть объект-обработчик, т.е. то, что мы коннектим к signal
в реализации(внутри) он, конечно, signal создает объект slot, но это происходит прозрачно для пользователя,
т.е. для Вас есть только signal и объект-обработчик (и никаких slot)
объекту signal передается объект-обработчик
но даже если передается адрес функции это соединение тоже можно отключить, а именно:
при создание связи "signal"-"slot" возвращает объект connection, при этом есть два варианта: соединение живет до уничтожения объекта connection или дольше, рекоммендую взглянуть в реализацию boost::signals:
{
scoped_connection c = sig.connect(hello);
}// соединение убъется при выходе из блока
{
connection c = sig.connect(hello);
}// соединение не убъется при выходе из блока
// присваивать к connection, конечно, не обязательно, но в этом случае
// разрыв произойдет только при уничтожениии sig
// при этом объект-обработчик может быть назначен различным сигналам
Очевидно что при таком подходе связь будет разорвана только при уничтожении источника события(сервера) если обработчик события (клиент) будет уничтожен раньше програма долго не проживет. В реальной жизни клиент как правило уничтожается раньше сервера. Пример документ(сервер) клиент (вид) сначала уничтожаются виды потом документ. Поэтому важно разрывать связь в любом случае, что здесь и сделано (см. код ;) )
А иначе прийдется ручками разрывать :)
Эта реализация тоже позволяет. Добавляется несколько несколько slot-ов в класс. Можно для каждого слота по функции обработчику или одну на всех (если тип аргумента сиглала совпадает)
class MyClass {
slot onSignal0, onSignaд1, .... // до бесконечности
}
но как раз дело в том, что для каждого сигнала требуется свой обработчик, т.е. получается
придется для варианта сдвумя сигналами и одним обработчиком писать след. конструкцию:
т.к. хендлеры выстраиваются в двусвязный список, и передачя одного и тогоже хендлера потянет за собой другие в цепочке, т.е. цепочки двух сигналов склеятся :-)
IMHO так, если есть другой способ поправте меня...
т.е. учитывая что оба сигнала не знают друг о друге, а хендлеры передаються им...
Здравствуйте, yaroslav_v, Вы писали:
_>Вы приводите указатель на функцию-член класса клиента _>к уназателю на функцию из конкрентного класса (slot::Thunk), _>это для некоторых классов может быть невозможно, _>ошибка компилятора, что-то типа "указатели имеют разную природу", _>наблюдатась для WTL проекта, я в свое время не стал _>углубляться, удалось обойтись
Ошибку на такое приведение может выдать только некорректный компилятор. Такое приведение всегда доложно прекрасно выполняться при помощи 'reinterpret_cast'.
_>В C++ указатели на функцию-член не всегда просто _>адрес функции, нельзя приводить указатель на функцию одного _>класса к указателю на функцию другого.
Указатель на функцию одного класса в С++ можно приводить к указателю на функцию любого другого класса при помощи 'reinterpret_cast'. Портабельное применение такого приведения типа ограничивается толкро тем, что рано или поздно придется сделать обратное преобразование и, согласно спецификации языка, при этом получится исходное значение. Именно на этом принципе построен данный код. Прямое преобразование делается в при инициализации слота, обратное — при вызове функции.
Best regards,
Андрей Тарасевич
Re[2]: Не всегда корректный код
От:
Аноним
Дата:
22.06.03 19:23
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:
<...>
АТ>Указатель на функцию одного класса в С++ можно приводить к указателю на функцию любого другого класса при помощи 'reinterpret_cast'.
Т.е. это проблемы MSVC, что у него указатель на функцию-член бывает всех размеров — от 4 до 12, и зависит от вируальности функции и наследования ?
<...>>
АТ>>Указатель на функцию одного класса в С++ можно приводить к указателю на функцию любого другого класса при помощи 'reinterpret_cast'.
А>Т.е. это проблемы MSVC, что у него указатель на функцию-член бывает всех размеров — от 4 до 12, и зависит от вируальности функции и наследования ?
И да, и нет. Разноразмерные указатели у MSVC бывают при умолчатеьных установках проекта: модель указателя на член класса = 'Best case always'. Зайди в установки проекта и явно включи более общую модель указателя на член класса — и все станет в порядке.
Не так всё просто. Там же сначала кастуется к указателю на член Thunk, а потом прямо так и вызывается, как метод Thunk. Информация о типе Owner уже потеряна, так что даже для универсального представления указателей вызов теоретически может быть неверным. В любом случае это неправильно — преобразовывать указатели на члены друг в друга. Это борьба с системой типов, которая выходит боком.
Здравствуйте, Viktor Sazhaev, Вы писали:
VS>Не так всё просто. Там же сначала кастуется к указателю на член Thunk, а потом прямо так и вызывается, как метод Thunk. Информация о типе Owner уже потеряна, так что даже для универсального представления указателей вызов теоретически может быть неверным. В любом случае это неправильно — преобразовывать указатели на члены друг в друга. Это борьба с системой типов, которая выходит боком.
Программирования без борьбы не бывает, главное чтобы эта борьба была осознанной. Я почи во всех сових проектах юзаю эту библиотеку и ни разу не прогорел. В реальной жизни в слот передается указатель и функции из одного класса. Согласитесь делать slot в одном классе, а handler в другом (даже в базовом) не есть хороший стиль программирования. Когда slot и handler находтся в одном классе проблем возникнуть не может по определению, независимо от того есть множественное наследование или нет.
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 байта — очевидно, что это некорректно.
)
более того у тебя явно зашит тип указателя "Owner*", это естественно не самый лучший метод — например
void onEvent( const char *eventName ) const
уже не будет компилиться — лечится заменой "Owner*" добавлением нового шаблонного параметра "class OwnerPtrTy" и
заменой "Owner*" на "OwnerPtrTy", это кстати позволит связывать signal не только с обычными указателями, но и со
"smart-pointer'ами", что в современных программах на C++ нужно не редко.