Вопрос

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

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

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

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

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

Я пробовал понижать значение указателя, но на самом деле это не сработало.

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

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

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

Это было полезно?

Решение

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

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

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

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

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

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

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

Другие советы

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

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;
}

Проблема здесь в том, что 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 *.

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

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

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

void (GameState::*EventPointer)()

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

Однако!Поскольку вы выполняете обратные вызовы (по сути, шаблон наблюдателя), вы можете внимательно взглянуть на повышение::сигналы.Он сделает все это (и даже больше).Вы не обязаны добавлять функции в 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));
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top