Using generic std::function objects with member functions in one class

前端 未结 6 2123
遥遥无期
遥遥无期 2020-11-22 09:41

For one class I want to store some function pointers to member functions of the same class in one map storing std::function objects. But I fail rig

相关标签:
6条回答
  • 2020-11-22 10:13

    A non-static member function must be called with an object. That is, it always implicitly passes "this" pointer as its argument.

    Because your std::function signature specifies that your function doesn't take any arguments (<void(void)>), you must bind the first (and the only) argument.

    std::function<void(void)> f = std::bind(&Foo::doSomething, this);
    

    If you want to bind a function with parameters, you need to specify placeholders:

    using namespace std::placeholders;
    std::function<void(int,int)> f = std::bind(&Foo::doSomethingArgs, this, std::placeholders::_1, std::placeholders::_2);
    

    Or, if your compiler supports C++11 lambdas:

    std::function<void(int,int)> f = [=](int a, int b) {
        this->doSomethingArgs(a, b);
    }
    

    (I don't have a C++11 capable compiler at hand right now, so I can't check this one.)

    0 讨论(0)
  • 2020-11-22 10:13

    You can avoid std::bind doing this:

    std::function<void(void)> f = [this]-> {Foo::doSomething();}
    
    0 讨论(0)
  • 2020-11-22 10:21

    Unfortunately, C++ does not allow you to directly get a callable object referring to an object and one of its member functions. &Foo::doSomething gives you a "pointer to member function" which refers to the member function but not the associated object.

    There are two ways around this, one is to use std::bind to bind the "pointer to member function" to the this pointer. The other is to use a lambda that captures the this pointer and calls the member function.

    std::function<void(void)> f = std::bind(&Foo::doSomething, this);
    std::function<void(void)> g = [this](){doSomething();};
    

    I would prefer the latter.

    With g++ at least binding a member function to this will result in an object three-pointers in size, assigning this to an std::function will result in dynamic memory allocation.

    On the other hand, a lambda that captures this is only one pointer in size, assigning it to an std::function will not result in dynamic memory allocation with g++.

    While I have not verified this with other compilers, I suspect similar results will be found there.

    0 讨论(0)
  • 2020-11-22 10:26

    If you need to store a member function without the class instance, you can do something like this:

    class MyClass
    {
    public:
        void MemberFunc(int value)
        {
          //do something
        }
    };
    
    // Store member function binding
    auto callable = std::mem_fn(&MyClass::MemberFunc);
    
    // Call with late supplied 'this'
    MyClass myInst;
    callable(&myInst, 123);
    

    What would the storage type look like without auto? Something like this:

    std::_Mem_fn_wrap<void,void (__cdecl TestA::*)(int),TestA,int> callable
    

    You can also pass this function storage to a standard function binding

    std::function<void(int)> binding = std::bind(callable, &testA, std::placeholders::_1);
    binding(123); // Call
    

    Past and future notes: An older interface std::mem_func existed, but has since been deprecated. A proposal exists, post C++17, to make pointer to member functions callable. This would be most welcome.

    0 讨论(0)
  • 2020-11-22 10:29

    Either you need

    std::function<void(Foo*)> f = &Foo::doSomething;
    

    so that you can call it on any instance, or you need to bind a specific instance, for example this

    std::function<void(void)> f = std::bind(&Foo::doSomething, this);
    
    0 讨论(0)
  • 2020-11-22 10:29

    You can use functors if you want a less generic and more precise control under the hood. Example with my win32 api to forward api message from a class to another class.

    IListener.h

    #include <windows.h>
    class IListener { 
        public:
        virtual ~IListener() {}
        virtual LRESULT operator()(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
    };
    

    Listener.h

    #include "IListener.h"
    template <typename D> class Listener : public IListener {
        public:
        typedef LRESULT (D::*WMFuncPtr)(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 
    
        private:
        D* _instance;
        WMFuncPtr _wmFuncPtr; 
    
        public:
        virtual ~Listener() {}
        virtual LRESULT operator()(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) override {
            return (_instance->*_wmFuncPtr)(hWnd, uMsg, wParam, lParam);
        }
    
        Listener(D* instance, WMFuncPtr wmFuncPtr) {
            _instance = instance;
            _wmFuncPtr = wmFuncPtr;
        }
    };
    

    Dispatcher.h

    #include <map>
    #include "Listener.h"
    
    class Dispatcher {
        private:
            //Storage map for message/pointers
            std::map<UINT /*WM_MESSAGE*/, IListener*> _listeners; 
    
        public:
            virtual ~Dispatcher() { //clear the map }
    
            //Return a previously registered callable funtion pointer for uMsg.
            IListener* get(UINT uMsg) {
                typename std::map<UINT, IListener*>::iterator itEvt;
                if((itEvt = _listeners.find(uMsg)) == _listeners.end()) {
                    return NULL;
                }
                return itEvt->second;
            }
    
            //Set a member function to receive message. 
            //Example Button->add<MyClass>(WM_COMMAND, this, &MyClass::myfunc);
            template <typename D> void add(UINT uMsg, D* instance, typename Listener<D>::WMFuncPtr wmFuncPtr) {
                _listeners[uMsg] = new Listener<D>(instance, wmFuncPtr);
            }
    
    };
    

    Usage principles

    class Button {
        public:
        Dispatcher _dispatcher;
        //button window forward all received message to a listener
        LRESULT onMessage(HWND hWnd, UINT uMsg, WPARAM w, LPARAM l) {
            //to return a precise message like WM_CREATE, you have just
            //search it in the map.
            return _dispatcher[uMsg](hWnd, uMsg, w, l);
        }
    };
    
    class Myclass {
        Button _button;
        //the listener for Button messages
        LRESULT button_listener(HWND hWnd, UINT uMsg, WPARAM w, LPARAM l) {
            return 0;
        }
    
        //Register the listener for Button messages
        void initialize() {
            //now all message received from button are forwarded to button_listener function 
           _button._dispatcher.add(WM_CREATE, this, &Myclass::button_listener);
        }
    };
    

    Good luck and thank to all for sharing knowledge.

    0 讨论(0)
提交回复
热议问题