4

私のイベントマネージャー

イベント マネージャーの場合、イベントがトリガーされたときに関数を呼び出すために、関数への多くのポインターをベクターに格納する必要があります。(この質問の最後に、EventFunction ヘルパー クラスのソース コードを提供します。)

// an event is defined by a string name and a number
typedef pair<string, int> EventKey;

// EventFunction holds a pointer to a listener function with or without data parameter
typedef unordered_map<EventKey, vector<EventFunction>> ListEvent;

// stores all events and their listeners
ListEvent List;

リスナーの登録は、追加データを受信するかどうかに応じて、最初または 2 番目の関数を呼び出すことで実行できます。(このコードは、私のイベント マネージャー クラスからのものです。)

public:
  typedef void (*EventFunctionPointer)();
  typedef void (*EventFunctionPointerData)(void* Data);

  // let components register for events by functions with or without data parameter,
  // internally simple create a EventFunction object and call the private function
  void ManagerEvent::Listen(EventFunctionPointer Function, string Name, int State);
  void ManagerEvent::Listen(EventFunctionPointerData Function, string Name, int State);

private:
  void ManagerEvent::Listen(EventFunction Function, string Name, int State)
  {
    EventKey Key(Name, State);
    List[Key].push_back(Function);
  }  

メンバー関数ポインター

リストに関数ポインターは格納しますが、メンバー関数ポインターは格納しないため、そのコードは機能しません。これらのポインターはすべてメンバー関数ポインターである必要があります。これは、イベントがトリガーされた場合に適切なサウンドを再生するために、そのメンバー関数のいずれかを使用してコンポーネントComponentSoundがイベントをリッスンするためです。"PlayerLevelup"ComponentSound::PlayerLevelup

C++ のメンバ関数ポインタは次のようになります。

// ReturnType (Class::*MemberFunction)(Parameters);
void (ComponentSound::*PlayerLevelup)();

問題は、任意のコンポーネント クラスがイベントをリッスンできる必要があることですが、メンバー関数ポインターをイベント マネージャーに格納するには、リッスン クラスを指定する必要があります。例でわかるように、指定する必要がありますComponentSoundが、イベント マネージャーは単に任意のクラスへのメンバー関数ポインターのベクトルを持つ必要があります。

質問

これらの質問のいずれかへの回答は、私を大いに助けてくれます。

  • イベント マネージャーのベクター内の任意のメンバー関数への関数ポインターを格納するにはどうすればよいですか? (おそらく、すべてのリッスン機能が 1 つの抽象クラスから継承されていることが役立つでしょうComponent。)
  • 目的の機能を実現するために、別の方法でイベント マネージャーを設計するにはどうすればよいですか? (メッセージに string および int キーを使用したい。)

質問を一般的なものにしようとしましたが、さらに情報やコードが必要な場合はコメントしてください。

課題

メンバー関数ポインターのベクトルでは、ポインターだけでなく EventFunction を使用して、2 つのメッセージ型を提供します。1 つはデータ パラメーターを使用し、もう 1 つはデータ パラメーターを使用しません。

class EventFunction
{
private: EventFunctionPointer Pointer; EventFunctionPointerData PointerData; bool Data;
public:
    EventFunction(EventFunctionPointer Pointer) : Pointer(Pointer), PointerData(NULL), Data(false) { }
    EventFunction(EventFunctionPointerData PointerData) : PointerData(PointerData), Pointer(NULL), Data(true) { }
    EventFunctionPointer GetFunction() { return Pointer; }
    EventFunctionPointerData GetFunctionData() { return PointerData; } bool IsData() { return Data; }
    void Call(void* Data = NULL){ if(this->Data) PointerData(Data); else Pointer(); }
};
4

4 に答える 4

6

を使用する必要がありますstd::function。これは、一般的なコールバックを実現する唯一の方法です。関数オブジェクトの代わりに関数ポインターを使用するとすぐに、それはジェネリックではなくなり、ジェネリックになることはなく、ジェネリックにすることもできません。

unordered_map<string, vector<std::function<void()>>>

関数ポインターは良くないので、C++ で明示的に使用するべきではありません。std::bindstd::functionのコンストラクターなどのテンプレートにのみ渡されます。メンバー関数ポインターはさらに悪質です。

于 2012-10-20T12:33:53.767 に答える
5

ファンクターを使用してこれを実現できます。メンバー関数の周りにファンクターをラップすると、ファンクターからベクトルを作成できます。ファンクターは次のようになります。

template <class T> class MyFunctor
{
private:
    T* ObjectPtr;
    void (T::*MemberFunction) ();
public:
    void operator () ()
    {
        return (*this->ObjectPtr.*this->MemberFunction)();
    }
};

したがって、基本的にファンクターは()演算子をオーバーライドし、ファンクタークラスに格納されているメンバー関数を返します。ファンクターを異なるシグニチャーで動作させたい場合、ファンクターは非常に複雑になる可能性がありますが、この記事では詳細情報を入手できます。

http://www.codeproject.com/Articles/7112/Pointers-to-Member-Functions-and-Functors

于 2012-10-20T12:29:04.037 に答える
3

直接の回答ではありませんので、ご容赦ください。

始める前に: これは一般にObserverパターンと呼ばれます。Web 上には、パターンに関する多くの混乱した情報や多くの実装の失敗が見られるかもしれませんが、あなたが成功する可能性があることは誰にもわかりません。

オブジェクトの有効期間が制限されているため、オブジェクト参照のキャプチャがトリッキーであることを考慮していません。

したがって、実装の詳細を掘り下げる前であっても、古い参照を処理する方法を自問する必要があります。2 つの基本的な戦略があります。

  1. 古い参照を持たないということは、登録されたオブジェクトが破棄時に自動的に登録解除されることを意味します。このメカニズムは、基本クラスで分解できます。

  2. それらを検査するときに、良い参照と古い参照を区別する方法を持ち、古い参照を怠惰に収集します。shared_ptrこのメカニズムは、 /weak_ptrペアを使用して強制でき、のオブザーバーであることweak_ptrを認識できます。shared_ptr

どちらのソリューションも実行可能であり、どちらの実装も完璧ではありません。基本クラスのメカニズムは、クラス階層を実際に変更できることを前提としていますが、weak_ptrトリックは、すべての監視がヒープに割り当てられ、それらの有効期間が によって制御されることを前提としていweak_ptrます。

以下を使用して例をshared_ptr作成します (いくつかの C++11 機能を使用しますが、ここでは必須ではありません)。

class EventManager {
    typedef std::unique_ptr<Observer> OPtr;
    typedef std::vector<OPtr> Observers;
public:

    // Callback observers of "name"
    // Returns the number of observers so invoked
    size_t signal(std::string const& name) const {
        auto const it = _observers.find(name);
        if (it == _observers.end()) { return 0; }

        Observers& obs = it->second;

        size_t count = 0;
        auto invoker = [&count](OPtr const& p) -> bool {
            bool const invoked = p->invoke();
            count += invoked;
            return not invoked; // if not invoked, remove it!
        };

        obs.erase(std::remove_if(obs.begin(), obs.end(), invoker), obs.end());

        if (obs.empty()) { _observers.erase(it); }

        return count;
    }

    // Registers a function callback on event "name"
    void register(std::string const& name, void (*f)()) {
        _observers[name].push_back(OPtr(new ObserverFunc(f)));
    }

    // Registers an object callback on event "name"
    template <typename T>
    void register(std::string const& name, std::shared_ptr<T> const& p, void (T::*f)()) {
        _observers[name].push_back(OPtr(new ObserverMember<T>(p, f)));
    }


private:
    struct Observer { virtual ~Observer() {} virtual bool invoke() = 0; };

    struct ObserverFunc: Observer {
        ObserverFunc(void (*f)()): _f(f) {}

        virtual bool invoke() override { _f(); return true; }

        void (*_f)();
    };

    template <typename T>
    struct ObserverMember: Observer {
        ObserverT(std::weak_ptr<T> p, void (T::*f)()): _p(p), _f(f) {}

        virtual bool invoke() override {
            std::shared_ptr<T> p = _p.lock();
            if (not p) { return false; }

            p->*_f();
            return true;
        }

        std::weak_ptr<T> _p;
        void (T::*_f)();
    };

    // mutable because we remove observers lazily
    mutable std::unordered_map<std::string, Observers> _observers;
}; // class EventManager
于 2012-10-20T14:10:19.793 に答える
2

これは、関数 (またはメンバー関数) ポインターの代わりにポリモーフィズムを使用する必要がある典型的なケースです。

ご指摘のとおり、コンポーネント クラスは、イベントを表す仮想メソッドを含む共通クラス Component から継承する必要があります。

class Component
{
public:
    virtual void OnPlayerLevelUp()
    {
    }
};

class ComponentSound : public Component
{
public:
     // override it
    void OnPlayerLevelUp()
    {
        // do the actual work
    }
};

ListEvent タイプは次のようになります。

typedef unordered_map<EventKey, vector<Component*>> ListEvent;

イベントメソッドのオプションの void* パラメーターについては、オプションのパラメーターとして指定できますが、それが void* であるという事実は悪い兆候です (void* を使用すると、型の安全性が失われる可能性があります)。あなたが望むものを達成するための別の方法を探すこと。

于 2012-10-20T12:45:34.357 に答える