#c #design-patterns #event-handling #function-pointers
#c #шаблоны проектирования #обработка событий #функции-указатели
Вопрос:
У меня есть класс Mouse, который обрабатывает события мыши. Он состоит из ряда статических функций для простых вызовов типа «где это и т.д.», Но также имеет несколько нестатических элементов, а именно некоторые элементы обработки событий, когда он используется в качестве объекта. Однако у меня возникли проблемы с тем, как я могу разрешить любому объекту подписываться на события. В моей мыши.в файле h у меня есть следующие объявления: (извините за синтаксические ошибки, это из памяти)
typedef void (*MouseEvent)(Point pos,MouseButton button)
class Mouse {
MouseEvent m_downEvent;
//...
void HookMouseDown(MouseEvent handler);
void OnMouseDown();
}
…и в реализации…
void Mouse::HookMouseDown(MouseEvent handler) {
if (handler != NULL) m_downEvent = handler;
}
void Mouse::OnMouseDown() {
if (m_downEvent != NULL) m_downEvent(m_pos,m_button);
}
Теперь в коде моего подписчика казалось логичным подключить событие таким образом:
m_mouse.HookMouseDown(amp;MyClass::MouseDown);
Но моему компилятору (MVC2008) не нравится тот факт, что я передаю ему указатель на функцию-член, а указатель на свободную функцию. После некоторых исследований здесь я обнаружил, что изменение моего typedef на
typedef void (MyClass::*MouseEvent)(Point pos,MouseButton button)
он не будет жаловаться и будет работать нормально, но проблема в том, что это ограничивает подписчиков на событие только объектами MyClass. Нужно ли мне подключать шаблоны, чтобы разрешить любому объекту подписываться на эти события? Или это был бы плохой дизайн, позволяющий чему-либо использовать события мыши в первую очередь?
Ответ №1:
он не будет жаловаться и будет работать нормально, но проблема в том, что это ограничивает подписчиков на событие только объектами MyClass.
Нет, вы также сможете «вызывать» этот член через экземпляры производного класса.
Несмотря на это, проблема решалась много раз с использованием std::mem_fun_ptr (c 03) std::function<> (c 0x), std::bind (c 0x) и boost::bind .
Вот полный пример, смотрите его в прямом эфире https://ideone.com/mut9V:
#include <iostream>
struct MyBase
{
virtual void DoStuff(int, float) { std::cout << "Base" << std::endl; }
};
struct MyDerived : MyBase
{
virtual void DoStuff(int, float) { std::cout << "Derived" << std::endl; }
};
int main()
{
typedef void (MyBase::*memfun)(int, float);
memfun event(amp;MyBase::DoStuff);
MyBase base;
MyDerived derived;
(base.*event)(42, 3.14);
(derived.*event)(42, 3.14);
}
Комментарии:
1. И, как всегда, вы можете использовать
boost::function
иboost::bind
, если вы еще не используете компилятор C 11.2. Добавлен полный рабочий пример, демонстрирующий полиморфный вызов через указатель на функцию-член
Ответ №2:
Чтобы класс Mouse
обрабатывал указатели на функции-члены произвольных классов, вы могли бы сделать его шаблоном:
template<class T>
class Mouse {
typedef void (T::*MouseEvent)(Point pos,MouseButton button);
MouseEvent m_downEvent;
//...
void HookMouseDown(MouseEvent handler);
void OnMouseDown();
}
Комментарии:
1. какой из двух определений типа? мне это кажется ошибочным
2. @sehe: Я быстро прочитал вопрос, не понял последнюю часть. Ответ все еще действителен (просто удалил первое предложение).
Ответ №3:
Или это был бы плохой дизайн, позволяющий чему-либо использовать события мыши в первую очередь?
Я думаю, что довольно приятно использовать дизайн, основанный на событиях. С другой стороны, поддержка как свободных функций, так и функций-членов — это боль в заднице. Особенно, если вам также нужно предоставить экземпляр, который вызывает эту функцию.
Так совпало, что я наткнулся на Delegate
реализацию, которая точно касается этого вопроса. Взгляните на эту интересную статью.
Теперь HookMouseDown
может возвращать ссылку на внутренний делегат, который затем может быть привязан либо к свободной функции, либо к функции-члену:
mouse.HookMouseDown().Bind<MyClass, amp;MyClass::MyMember>(myinstance);
Ответ №4:
Вам действительно нужны std::function и std::bind / лямбды (функция / привязка также доступна в Boost). Это более чем адекватно решает проблему.