Указатель на функцию-член странным образом

#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). Это более чем адекватно решает проблему.