Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« : 24-11-2008 10:01 » |
|
Имеется в некой библиотеке некая функция, которая принимает аргументом другую функцию для последующего callback-вызова. Допустим: typedef void (*Handler)();
void registerHandler(Handler handler); Очень хочется передать туда такую функцию, внутри которой был бы известен контекст её исполнения (объект). Просто создать класс с оператором "()" недостаточно, поскольку тип MyClass::operator() не соответствует типу Handler. Адаптеры STL типа mem_fun построены так, что при вызове результата этих адаптеров типа mem_fun_t требуется передать аргументом объект. Можно попробовать убрать этот аргумент, но адаптеры STL типа bind1st, если я правильно понимаю, возвращают объект класса binder1st - и это возвращает к исходной ситуации. Можно ли исхитриться?
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #1 : 24-11-2008 12:33 » |
|
Есть мнение, что никак.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Вад
|
|
« Ответ #2 : 24-11-2008 12:44 » |
|
Я так понимаю, вариант "синглетон" не подходит?
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #3 : 24-11-2008 13:44 » |
|
Вад, это частный случай. Общий случай - много объектов одного класса. Нужно что-то вроде замыкания (closure), известного из некоторых скриптовых языков.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Вад
|
|
« Ответ #4 : 24-11-2008 13:50 » |
|
dimka, ну, если библиотека не предполагает многопоточного использования - то подошло бы вполне: устанавливаешь контекст и работаешь.
А этот handler - он дальше как используется, просто происходит callback при каждом событии для всех подписавшихся? или?
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #5 : 24-11-2008 15:28 » |
|
Вад, это уже детали пошли На самом деле, как тут выяснилось, вместе с регистрацией обработчика передаётся ID контекста, который затем подаётся аргументом в обработчик. Так что в моей конкретике проблемы нет вовсе. И остаётся лишь исходный вопрос.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
npak
|
|
« Ответ #6 : 24-11-2008 18:09 » |
|
Есть такая возможность, даже две.
1. Число одновременно зарегистрированных хендлеров конечно и не превосходит некоторой константы, например MAX_HANDLER. Тогда создаешь массив контекстов contexts[MAX_HANDLER] и массив функций handlers[MAX_HANDLER], причем в функции handlers[ i ] жестко прописано использовать в качестве контекста contexts[ i ]
Сценарий использования - выбираешь какое-то значение i, устанавливаешь значение контекста contexts[ i ], затем регистрируешь handlers[ i ].
2. второй способ несколько сложнее в реализации - можно на ходу гененрировать код хендлера. Но это получится уже самодифицирующая программа, что не есть хорошо.
|
|
« Последнее редактирование: 24-11-2008 18:13 от npak »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #7 : 24-11-2008 20:44 » |
|
npak, не понял. Контекст по сути своей представляет экземпляр некой структуры или класса. Обработчик - это функция. Функция одна, класс один - в случае синглетона вопроса нет. А вот когда экземпляров много, то как определить, с каким контекстом работать этой единственной функции?
Если отдельно описывать функцию для каждого экземпляра, то никакой динамики в программе не будет - это вариант с N синглетонами.
Предлагаемый выше вариант с предварительной установкой контекста в некую глобальную переменную в моём конкретном случае не подходил, поскольку вызов обработчиков происходил не в порядке их регистрации, а в порядке, определяемом внутренней логикой библиотеки. И можно было зарегистрировать несколько обработчиков.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #8 : 24-11-2008 22:40 » |
|
dimka, Глобальный STL::map. Если он не нравится, можно и в класс синглетон его положить. В нем делаеш связку ID - экземпляр объекта. В принципе почти тоже самое и предложил npak. Теперь в своей callback функции, судя по твоим словам, ты уже знаеш ID. Первым делом обрашаешся к синглетону и по ID получаеш ссылку на экземпляр класса. Далее уже дело техники. Если ID формируется тобой, а не библиотекой и имеет тип int, можно конечно через него и передавать сам адрес на экземпляр класса. Правда не всегда такой подход будет работать. Сейчас посмотрел на твое определение функции typedef void (*Handler)();
Интересно, как библиотека ID передает в callback функцию?
|
|
« Последнее редактирование: 24-11-2008 22:44 от Finch »
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
Антон (LogRus)
|
|
« Ответ #9 : 25-11-2008 07:11 » |
|
А может всё же функтор поиспользовать, чем огород городить. Периодически приходится всякие функторы хранить в контейнере, внутри объекта и еще где-то, куда-то меня лично страивает В качестве функтора, использую boost::function, как результат boost::bind typedef boost::function< void ()> Handler;
void registerHandler(Handler handler) { std::cout << "registerHandler\n" ; handler(); }
struct A { doit(){ std::cout << "A::doit\n" ;} };
struct B { doit2(){ std::cout << "A::doit2\n" ;} };
A a; B b; registerHandler(boost::bind(&A::doit, &a)); registerHandler(boost::bind(&B::doit2, &b));
|
|
|
Записан
|
Странно всё это....
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #10 : 25-11-2008 10:44 » |
|
Теперь в своей callback функции, судя по твоим словам, ты уже знаеш ID. Я же написал, что поскольку на самом деле есть ID, то проблемы нет. Именно через map и сделали сразу же. Я говорю, что остаётся исходный вопрос - когда нет никаких ID. typedef boost::function< void ()> Handler;
void registerHandler(Handler handler) { std::cout << "registerHandler\n" ; handler(); }
Так не пойдёт. Если бы была моя библиотека, я бы вообще подписывал не функцию, а объект с известным интерфейсом - по шаблону "Наблюдатель". В чужой библиотеке я не могу переопределять тип Handler так, как мне вздумается. Я должен пользоваться таким типом, который есть.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #11 : 25-11-2008 11:16 » |
|
эхъ объясните мне, темноте, проблему на пальцах, не могу вникнуть (
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #12 : 25-11-2008 13:07 » |
|
Есть возможность сделать callback твоих функций (разных) из чужой библиотеки в ответ на какие-то события (разные). Твои функции могут быть подписаны на callback средствами библиотеки. При подписывании каждой твоей функции ты передаёшь дополнительные параметры (например, тип события, в ответ на которое будет вызываться подписываемая функция). Но когда происходит callback твоей функции, у неё в параметрах нет никакой дополнительной информации. В результате библиотека вынуждает тебя описывать отдельную функцию на каждый отдельный случай (например, на каждый отдельный тип события). Если к такой библиотеке писать объектную обёртку, то разницу хочется спрятать в отдельных экземплярах одного класса, а не писать каждый раз отдельную функцию. Спрашивается, возможно ли это? Например: Имеется. // Чужая библиотека. enum EventType { Event1, Event2 }; typedef void (*Handler)(); void registerHandler(Handler handler, EventType type); Нужно писать: // Твой код. void handleEvent1() { cout << "Event1" << endl; } void handleEvent2() { cout << "Event2" << endl; }
// Где-то ниже подписка. registerHandler(handleEvent1, Event1); registerHandler(handleEvent2, Event2); Чего хочется: // Твой код. class MyHandler { private: EventType event; string data; public: MyHandler(EventType event): event(event) {} void setData(string &data) { this->data = data; } EventType getEvent() { return this->event; } void operator()() { cout << this->data << endl; } };
typedef list<MyHandler> Handlers; Handlers handlers; handlers.push_back(MyHandler1(Event1)); handlers.push_back(MyHandler1(Event2)); handlers[0].setData("Event1"); handlers[1].setData("Event2");
// Где-то ниже подписка. for(Handlers::iterator ptrHandler = handlers.begin(); i != handlers.end(); ++i) registerHandler(*ptrHandler, ptrHandler->getEvent()); Цель - добиться динамики в подписке. Но так нельзя, потому что тип void MyHandler::operator()() не соответствует типу void Handler().
|
|
« Последнее редактирование: 25-11-2008 13:10 от dimka »
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
RXL
Технический
Администратор
Online
Пол:
|
|
« Ответ #13 : 25-11-2008 13:50 » |
|
Я бы сделал так: void event(int event_id) { ... }
void event_1(void) { event(1); } void event_2(void) { event(2); } void event_N(void) { event(N); }
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #14 : 25-11-2008 13:58 » |
|
RXL, опять же, частный случай, если есть EventType с обозримым количеством значений. А если там параметр типа int или того хуже double?
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
RXL
Технический
Администратор
Online
Пол:
|
|
« Ответ #15 : 25-11-2008 14:11 » |
|
Значения извесны при компиляции или множество динамическое?
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #16 : 25-11-2008 14:30 » |
|
Значения извесны при компиляции или множество динамическое? Читается из config-файла.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
zubr
Гость
|
|
« Ответ #17 : 25-11-2008 18:20 » |
|
dimka, посмотри на интерфейсную реализацию метода Invoke интерфейса IDispatch Windows. Там как раз этот метод позволяет реализовывать практически любое событие с различным количеством и типом параметров.
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #18 : 25-11-2008 20:26 » |
|
zubr, а к чему это? Это же метод объекта и, соответственно, контекст его исполнения определён. Чего не скажешь о функции.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
zubr
Гость
|
|
« Ответ #19 : 26-11-2008 07:54 » |
|
Есть возможность сделать callback твоих функций (разных) из чужой библиотеки в ответ на какие-то события (разные). Твои функции могут быть подписаны на callback средствами библиотеки. При подписывании каждой твоей функции ты передаёшь дополнительные параметры (например, тип события, в ответ на которое будет вызываться подписываемая функция). Но когда происходит callback твоей функции, у неё в параметрах нет никакой дополнительной информации. Я к тому, что параметры метода Invoke позволяют передавать в callback-функции любые параметры и с любыми типами. То есть если использовать принцип передачи параметров данного метода, то вообще можно отказаться от регистрации событий. К примеру: void MyEventClass::MyEvent1 (char* Param1, int Param2) { ..................... }
void MyEventClass::DispatchEvent (DISPPARAMS* pDispParams) { if ((pDispParams->cArgs == 2) && (pDispParams->rgvarg[0].vt == VT_BSTR) && (pDispParams->rgvarg[1].vt == VT_I4)) MyEvent1(pDispParams->rgvarg[0], pDispParams->rgvarg[1]); else ............................ }
IDispatch::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pvarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) { DispatchEvent (pDispParams);
}
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #20 : 26-11-2008 12:02 » |
|
Я к тому, что параметры метода Invoke позволяют передавать в callback-функции любые параметры и с любыми типами. То есть если использовать принцип передачи параметров данного метода, то вообще можно отказаться от регистрации событий. Как я могу менять набор параметров, когда тип обработчика и регистратора события определён в чужой библиотеке? Если бы чужая библиотека была настроена на вызов Invoke, то да. Так она реализована другим способом. Понятно, что если тип параметра "void *", то засунуть туда я могу всё, что угодно - лишь бы потом суметь это разобрать.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
|