Store Function Pointers to any Member Function

2019-02-07 11:09发布

问题:

My Event Manager

For a event manager I need to store many pointers to functions in a vector to call them when the event is triggered. (I will provide the source code of the EventFunction helper class at the end of this question.)

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

Registering an listener could be done by calling the first or the second function, depending on if you want receive additional data or not. (This code is from my event manager class.)

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

Member Function Pointers

That code doesn't work because I store function pointers but not member function pointers in my List. All these pointers should be member function pointers because a component like ComponentSound will listen to the event "PlayerLevelup" with on of its member functions ComponentSound::PlayerLevelup to play a nice sound if the event is triggered.

A member function pointer in C++ looks like this.

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

The problem is, any component class should be able to listen for events, but storing the member function pointers in the event manager requires me to specify the listening class. As you can see in the example, I need to specify ComponentSound but the event manager should simply have a vector of member function pointers to any class.

Question

An Answer to one of these question would help me a lot.

  • How can I store function pointers to any member function in a vector in my event manager? (Maybe it helps that all the listening functions are inherited from one abstract class Component.)
  • How can I design my event manager in another way to reach the aimed functionality? (I want to use string and int keys for messages.)

I tried to keep my question general but if you need more informations or code please comment.

Assignments

In my vector of member function pointers I use EventFunction instead of only a pointer to provide two message types. One with, and one without a data parameter.

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

回答1:

Not a direct response, so bear with me.

Before we start: This is generally referred to as the Observer pattern, you might find lots of confused information on the web about it, and many failed implementations, but who knows you might also strike gold.

Okay, so first the question has a fundamental flaw: it fails to consider that capturing object references is tricky, because object lifetimes are bounded.

Therefore, even before we delve into the specifics of an implementation we need to ask ourselves how to handle stale references. There are two basic strategies:

  1. Not having stale references, this implies that registered objects unregister themselves automatically upon destruction. The mechanism can be factored out in a base class.

  2. Having a way to tell good and stale references apart when inspecting them, and lazily collecting the stale ones. The mechanism can be enforced using a shared_ptr/weak_ptr pair and realizing that weak_ptr are observers of the shared_ptr.

Both solutions are viable and neither implementation is perfect. The base class mechanism assumes you can actually modify your class hierarchy while the weak_ptr trick assumes that all observes will be heap-allocated and their lifetime controlled by a weak_ptr.

I will make an example using shared_ptr (and make use of a number of C++11 facilities, though none is mandatory here):

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


回答2:

You will have to use std::function. This is the only way to achieve a generic callback. As soon as you involve function pointers instead of function objects, it is not generic, will never be generic, and can never be made to be generic.

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

Function pointers are bad and should never be explicitly used in C++, only passed to templates like std::bind and std::function's constructor, and member function pointers are even worse.



回答3:

You can use functors to achieve this. If you wrap a functor around your member functions you can make a vector out of functors. A functor looks like this:

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

So basically a functor overrides the () operator and returns the member function stored in the functor class. Functors can be quite complex if you want them to work with different signatures but in this article you can get further information.

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



回答4:

This is the typical case where you should use polymorphism instead of function (or member function) pointers.

As you noted, your component classes should inherit from a common class Component, which contains virtual method(s) representing the event(s):

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

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

Your ListEvent type will now look like this:

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

As for the optional void* paramenter in event methods, you can specify it as an optional parameter, but the fact that it's a void* is a bad sign (use of void* can lead to loss of type safety), so I would suggest that you look for a different way to achieve what you want.