在类中使用具有成员函数的通用std :: function对象


169

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

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

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

Answers:


301

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

由于您的std::function签名指定函数不带任何参数(<void(void)>),因此必须绑定第一个(也是唯一的)参数。

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 lambdas:

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

(我没有一个C ++在手部11能够编译器现在,所以我不能检查这一项。)


1
因为我不依赖boost,所以我将使用lambda表达式;)不过,谢谢!
Christian Ivicevic 2011年

3
@AlexB:Boost.Bind不使用ADL作为占位符,而是将它们放在匿名命名空间中。
ildjarn 2011年

46
我建议避免全局捕获[=],并使用[this]使其更清楚地捕获什么(Scott Meyers-有效的现代C ++第6章。项目31-避免默认捕获模式)
Max Raskin

5
只需添加一个小秘密:成员函数指针可以隐式转换为std::function与额外的this,因为它是第一个参数,比如std::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
landerlyoung

@landerlyoung:在函数名称上方添加“ f”,以修复示例语法。如果不需要名称,可以使用mem_fn(&Foo :: doSomethingArgs)。
Val

80

你要么需要

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

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

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

谢谢您的出色回答:D正是我所需要的,我找不到如何专门化std :: function来在任何类实例上调用成员函数。
penelope '18 -4-5

这可以编译,但是是标准的吗?您是否保证第一个参数是this
sudo rm -rf斜线

@ sudorm-rfslash是的,您是
Armen Tsirunyan,

感谢您的回复@ArmenTsirunyan ...我可以在标准位置哪里找到此信息?
sudo rm -rf斜线

13

如果需要在没有类实例的情况下存储成员函数,则可以执行以下操作:

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之后,存在一个建议,使指向成员函数的指针可调用。这将是最受欢迎的。


@Danh std::mem_fn除去 一堆不必要的过载。另一方面std::mem_fun,C ++ 11不推荐使用,而C ++ 17则将其删除。
Max Truxa

@Danh这正是我在说的;)第一个“基本”重载仍然存在:template<class R, class T> unspecified mem_fn(R T::*);,并且它不会消失。
Max Truxa

@Danh请仔细阅读DR。DR删除了13个过载中的12个。最后一个不是(也不会;在C ++ 11或C ++ 14中都不是)。
Max Truxa

1
为什么要投反对票?其他每个响应都说您必须绑定该类实例。如果要创建用于反射或脚本编制的绑定系统,则将不需要这样做。此替代方法对某些人有效且相关。
格雷格

谢谢Danh,我编辑了一些有关过去和将来的界面的评论。
格雷格,

3

不幸的是,C ++不允许您直接获取引用对象及其成员函数之一的可调用对象。&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 ++至少将一个成员函数绑定到此函数,将导致对象的大小为三指针,将其分配给的对象std::function将导致动态内存分配。

另一方面,捕获的lambda this大小仅为一个指针,将其分配给an std::function不会导致g ++动态分配内存。

尽管我尚未与其他编译器进行验证,但是我怀疑在那里会发现类似的结果。


1

如果您想要引擎盖下的通用性较低且更精确的控制,则可以使用仿函数。我的win32 api的示例将api消息从一个类转发到另一个类。

IListener.h

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

监听器

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

分派器

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

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


0

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

std::function<void(void)> f = [this]-> {Foo::doSomething();}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.