質問
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
}
ポインタをダウンキャストしてみましたが、うまくいきませんでした。
私のスプライトクラスにはこれらの 2 つの変数も含まれています
void (GameState::*m_EventPointer)();
GameState* m_LinkedGameState;
助けていただければ幸いです
解決
私は本当にあなたの割り当てが機能しない理由は、間違いなくlitb理由を説明するためにすぐに沿ったものになるかわかりません。 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));
これは、イベントオブジェクトにカプセル化されます。 - この方式では、あなたは本当に「リンクゲームの状態」を記憶する必要はありません。
他のヒント
私は非常に同様の問題がありました。私はこれを正しく読んでいる場合は、ほとんどのJavaで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
インスタンスで呼び出すことができないということです。それだけでタイプのMainMenuState
パラメータvoid (GameState::*)()
と呼ばれることができます。
Callback *
に規定された方法<=>点を有しているために、この方法はまた<=>で定義されている仮想メソッドでなければなりません。
私のようなものを使用して、「コマンドオブジェクト」(またはファンクタ)ポインタを格納、代わりにメンバ関数ポインタを格納しようとしているのをお勧めします:
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)();
};
あなたはその後、型のメンバを定義することができます<=>。
なぜあなたはそこにテンプレート機能を使用していますか? OnClick
GameState以外T
の値に対して、コンパイル、または動作しません。
しかし、このような状況で私は一般的にメンバ関数へのポインタを使用することはありません。私は、のオブジェクトの関数を作成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));