【问题标题】:Using generic std::function objects with member functions in one class在一个类中使用具有成员函数的通用 std::function 对象
【发布时间】:2011-11-26 19:23:12
【问题描述】:

对于一个类,我想在一个 map 存储 std::function 对象中存储一些指向同一类的成员函数的函数指针。但我一开始就用这段代码失败了:

#include <functional>

class Foo {
    public:
        void doSomething() {}
        void bindFunction() {
            // ERROR
            std::function<void(void)> f = &Foo::doSomething;
        }
};

我在xxcallobj 中收到了error C2064: term does not evaluate to a function taking 0 arguments 以及一些奇怪的模板实例化错误。目前,我正在使用 Visual Studio 2010/2011 在 Windows 8 上工作,而在使用 VS10 的 Win 7 上它也失败了。该错误必须基于我不遵循的一些奇怪的 C++ 规则

【问题讨论】:

    标签: c++ function function-pointers c++11 tr1


    【解决方案1】:

    必须使用对象调用非静态成员函数。也就是说,它总是隐式传递“this”指针作为参数。

    因为您的 std::function 签名指定您的函数不接受任何参数 (&lt;void(void)&gt;),所以您必须绑定第一个(也是唯一的)参数。

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

    如果要绑定带参数的函数,需要指定占位符:

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

    或者,如果您的编译器支持 C++11 lambda:

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

    (我手头没有支持 C++11 的编译器现在,所以我无法检查这个。)

    【讨论】:

    • 由于我不依赖于 boost 我将使用 lambda 表达式 ;) 不过谢谢!
    • @AlexB : Boost.Bind 不对占位符使用 ADL,而是将它们放在匿名命名空间中。
    • 我建议避免全局捕获 [=] 并使用 [this] 使捕获的内容更清晰(Scott Meyers - Effective Modern C++ Chapter 6. item 31 - Avoid default capture patterns)
    • 补充一点提示:成员函数指针可以隐式转换为std::function,第一个参数是额外的this,如std::function&lt;void(Foo*, int, int)&gt; = &amp;Foo::doSomethingArgs
    • @landerlyoung:将函数名称添加为上面的“f”以修复示例语法。如果您不需要名称,可以使用 mem_fn(&Foo::doSomethingArgs)。
    【解决方案2】:

    任你选择

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

    这样你就可以在任何实例上调用它,或者你需要绑定一个特定的实例,例如this

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

    【讨论】:

    • 感谢您的出色回答:D 正是我需要的,我找不到如何专门化 std::function 以在任何类实例上调用成员函数。
    • 这可以编译,但它是标准的吗?你保证第一个参数是this吗?
    • @sudorm-rfslash 是的,你是
    • 感谢@ArmenTsirunyan 的回复......我可以在标准中的哪里找到这些信息?
    【解决方案3】:

    如果你需要存储一个成员函数没有类实例,你可以这样做:

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

    如果没有 auto,存储类型会是什么样子? 像这样的:

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

    您也可以将此函数存储传递给标准函数绑定

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

    过去和未来的注释:旧接口 std::mem_func 存在,但已被弃用。在 C++17 之后存在一个提议,以创建 pointer to member functions callable。这将是最受欢迎的。

    【讨论】:

    • @Danh std::mem_fn删除;一堆不必要的重载。另一方面,std::mem_fun 已在 C++11 中弃用,并将在 C++17 中删除。
    • @Danh 这正是我要说的 ;) 第一个“基本”重载仍然存在:template&lt;class R, class T&gt; unspecified mem_fn(R T::*);,它不会消失。
    • @Danh 请仔细阅读the DR。 DR 移除了 13 个过载中的 12 个。最后一个不是(也不会是;在 C++11 或 C++14 中都不是)。
    • 为什么投反对票?每个其他响应都说您必须绑定类实例。如果您正在为反射或脚本创建绑定系统,您将不想这样做。这种替代方法对某些人有效且相关。
    • 感谢 Danh,我已经与一些 cmets 一起编辑了有关过去和未来相关界面的内容。
    【解决方案4】:

    不幸的是,C++ 不允许您直接获取引用对象及其成员函数之一的可调用对象。 &amp;Foo::doSomething 给你一个“指向成员函数的指针”,它指的是成员函数,但 不是 关联的对象。

    有两种方法,一种是使用std::bind将“指向成员函数的指针”绑定到this指针。另一种是使用捕获this指针并调用成员函数的lambda。

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

    我更喜欢后者。

    使用 g++ 至少将成员函数绑定到 this 将导致对象大小为三指针,将 this 分配给 std::function 将导致动态内存分配。

    另一方面,捕获 this 的 lambda 大小只有一个指针,将其分配给 std::function 不会导致使用 g++ 进行动态内存分配。

    虽然我没有用其他编译器验证这一点,但我怀疑在那里会找到类似的结果。

    【讨论】:

      【解决方案5】:

      你可以避免std::bind这样做:

      std::function<void(void)> f = [this]-> {Foo::doSomething();}
      

      【讨论】:

        【解决方案6】:

        如果您希望在后台进行不那么通用且更精确的控制,则可以使用函子。以我的 win32 api 为例,将 api 消息从一个类转发到另一个类。

        ILListener.h

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

        监听器.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);
                }
        
        };
        

        使用原则

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

        祝你好运,感谢大家分享知识。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多