Полиморфный член-указатель на функцию - PullRequest
1 голос
/ 26 июня 2009

Я пытаюсь написать систему событий обратного вызова в DirectX9. Я пытаюсь использовать указатели на функции метода для запуска событий щелчками мыши; но у меня есть некоторые проблемы. Моя игра использует менеджер состояния игры для управления рендерингом. Все мои игровые состояния являются производными от базового класса AbstractGameState.

У меня есть объект спрайта с этим конкретным методом:

m_NewGameSprite->OnClick(this, &MainMenuState::StartGame);

MainMenuState - это текущее игровое состояние, в котором находится моя игра, а StartGame является частью метода void этого класса. Я хотел бы сохранить указатель функции в переменной внутри моего класса спрайтов, чтобы я мог выполнить его, когда пользователь щелкнет.

template <typename T>
void OnClick(GameState* LinkedState, void (T::*EventPointer)())
{
    m_LinkedGameState = LinkedState;
    m_EventPointer = EventPointer; // <- Doesnt compile
}

Я пытался уменьшить указатель, но это не сработало.

Мой класс спрайтов также содержит эти две переменные

void                (GameState::*m_EventPointer)();
GameState*          m_LinkedGameState;

Буду признателен за любую помощь

Ответы [ 5 ]

2 голосов
/ 26 июня 2009

Я действительно не знаю, почему ваше задание не работает, без сомнения, в скором времени будет объяснение, почему. Boost.Function - это красивый, универсальный, типобезопасный и стандартный объект функции, который можно использовать в качестве замены для указателей на функции практически при любых обстоятельствах. Я бы сделал это:

typedef boost::function0<void> Event;
Event m_Event;

Обратите внимание, что класс событий теперь инкапсулирует состояние вызова функции, включая объект, для которого вы хотите вызвать его. Обычно вы также используете Boost.Bind для создания замыкания, но вы также можете легко привязать к свободной функции или к другому объекту функции.

void OnClick(Event &event)
{
  m_Event=event;
}

Назовите это так:

OnClick(boost::bind(&MainMenuState::StartGame, this));

При такой схеме вам на самом деле не нужно хранить «связанное игровое состояние» - оно инкапсулировано в объекте события.

1 голос
/ 11 августа 2012

У меня была очень похожая проблема. Если я читаю это правильно, вы пытаетесь написать систему, почти как ActionEvent в Java. Который может выглядеть так:

Component.addActionListener( new ActionEventListener( new ActionEvent(
            public void execute() { /* Component clicked Do Something */ } )));

В C ++ есть очень похожая вещь, как показано ниже.

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

Event.hpp

#pragma once

class Event 
{
    public:
        Event(void) { }
        ~Event(void) { }

    public:
        //Stores a function to callback. Example shown in Main.cpp
        void operator += (void (*callback)())
        {
            this->callback = callback;
        }

        //Executes our stored call back method at any given time.
        void execute(void)
        {
            callback();
        }

        //Variable of type VOID* -> This is a function which is stored + executed.
        private:
            void (*callback)();
};

main.cpp

#include <iostream>
#include "Event.hpp"

using namespace std;

void print(void)
{
    cout << "Hello This Works" << endl;
}

void print2(void)
{
    cout << "Success again!" << endl;
}

int main()
{
    Event task_event;
    task_event += print;
    task_event.execute();
    task_event += print2;
    task_event.execute();
    system("pause");
    return 0;
}
1 голос
/ 26 июня 2009

Проблема в том, что StartGame нельзя вызвать ни с одним экземпляром GameState, использующим его параметр this. Он может быть вызван только с параметром this типа MainMenuState.

Чтобы иметь void (GameState::*)() точку для метода, определенного в MainMenuState, метод должен быть виртуальным методом, который также определен в GameState.

Я бы рекомендовал вместо того, чтобы пытаться сохранить указатель на функцию-член, сохранить указатель «объект команды» (или функтор), используя что-то вроде:

class Callback
{
public:
    virtual void Execute() = 0;
};

И затем определение реализации следующим образом:

template <typename TClass>
class MemberCallback : public Callback
{
public:

    MemberCallBack(TClass * pThis, void (TClass::*pFunc)() )
    {
        m_pThis = pThis;
        m_pFunc = pFunc;
    }
    void Execute()
    {
        m_pThis->*m_pFunc();
    }

private:
    TClass * m_pThis;
    void (TClass::*m_pFunc)();
};

Затем вы можете определить член типа Callback *.

0 голосов
/ 26 июня 2009

Вы должны принять параметр как:

void (GameState::*EventPointer)()

Потому что, как только он является указателем, он не может быть так понижен (у него нет информации о том, существует ли такой же указатель в базовом классе). Функции должны быть на GameState, чтобы это работало (возможно, виртуально)

Однако! Поскольку вы выполняете обратные вызовы (в основном шаблон наблюдателя), вы можете долго смотреть на boost :: сигналов . Это сделает все это (и даже больше). Вам не нужно добавлять функции в GameState.

class Foo {
 // ...
 template <typename T>
 boost::signals::connection OnClick(const boost::function<void ()>& func)
 {
    return sig.connect(func);
 }

 void FireClick() { sig_(); }
private:
  boost::signal<void ()> sig_;
};

int this_will_be_called() { }

Foo f = // ...;

f.OnClick(boost::bind(&this_will_be_called));
0 голосов
/ 26 июня 2009

Почему вы используете там функцию шаблона? OnClick не будет компилироваться или работать для значений T, отличных от GameState.

Однако в этой ситуации я обычно не использовал бы указатель на функцию-член. Я бы создал функцию object , перегружающую operator(), и вместо этого передавал бы их. Это лучше: синтаксис более ясен, и вы можете сохранить некоторое состояние в объекте функции, если обнаружите, что вам нужно.

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