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;

可以通过调用第一个或第二个函数来注册侦听器,具体取决于您是否要接收其他数据。(此代码来自我的事件管理器类。)

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,但事件管理器应该简单地具有指向任何类的成员函数指针向量。

问题

对其中一个问题的回答对我有很大帮助。

  • 如何在事件管理器的向量中存储指向任何成员函数的函数指针?(也许所有的监听函数都继承自一个抽象类会有所帮助Component。)
  • 如何以另一种方式设计我的事件管理器以实现目标功能?(我想对消息使用字符串和整数键。)

我试图让我的问题保持一般性,但如果您需要更多信息或代码,请发表评论。

作业

在我的成员函数指针向量中,我使用 EventFunction 而不是仅使用指针来提供两种消息类型。一个有数据参数,一个没有数据参数。

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::bindandstd::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模式,您可能会在网络上找到很多关于它的混淆信息,以及许多失败的实现,但谁知道您也可能会获得成功。

好的,首先这个问题有一个基本缺陷:它没有考虑到捕获对象引用是棘手的,因为对象的生命周期是有限的。

因此,甚至在我们深入研究实现的细节之前,我们需要问自己如何处理过时的引用。有两种基本策略:

  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 回答