在一个类中使用通用的std::function对象与成员函数

249

我想在一个map中存储同一类的成员函数的函数指针,同时存储std::function对象。但是我尝试使用以下代码失败了:

#include <functional>

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

我在使用 xxcallobj 时遇到了 错误 C2064:术语不对应,该术语不接受0个参数的函数 和一些奇怪的模板实例化错误。目前我正在使用Windows 8和Visual Studio 2010/2011进行工作,在Win 7上使用VS10也会失败。这个错误一定是基于某些我没有遵循的奇怪的C++规则。

6个回答

425
一个非静态成员函数必须使用对象调用。也就是说,它总是隐式地将 "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的lambda表达式:

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

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


1
由于我不依赖boost,所以我将使用lambda表达式 ;) 尽管如此,还是要谢谢! - Christian Ivicevic
3
Boost.Bind在占位符中没有使用ADL,而是将它们放在一个匿名命名空间中。 - ildjarn
66
我建议避免全局捕获 [=],使用 [this] 可以更清楚地表明被捕获的内容。(Scott Meyers - 《Effective Modern C++》第6章第31条 - 避免默认捕获模式) - Max Raskin
7
只需添加一点提示:成员函数指针可以隐式转换为std::function,需要额外将this指针作为其第一个参数传入,例如std::function<void(Foo*, int, int)> = &Foo::doSomethingArgs。请注意不要改变原意。 - landerlyoung
@landerlyoung:将函数say的名称更改为上方的“f”以修复示例语法。如果您不需要名称,则可以使用mem_fn(&Foo :: doSomethingArgs)。 - Val
为了做到这一点,需要使用#include<functional> - Minwoo Yu

117

你要么需要

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

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

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

1
非常感谢您的出色回答:D 正是我所需要的,我找不到如何将std :: function专门用于调用任何类实例上的成员函数。 - penelope
1
这个可以编译,但是它是否符合标准呢?你能保证第一个参数是this吗? - sudo rm -rf slash
@sudorm-rfslash 是的,你是。 - Armen Tsirunyan
1
谢谢您的回复@ArmenTsirunyan...我在标准中可以找到这个信息吗? - sudo rm -rf slash

18

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

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
请仔细阅读DR。 DR已经删除了13个重载中的12个,但最后一个不会被删除(无论是在C++11还是C++14中)。 - Max Truxa
1
为什么会有人点踩?其他所有的回答都说你必须绑定类实例。如果你正在为反射或脚本编写绑定系统,那么你不想这样做。这种替代方法对某些人来说是有效和相关的。 - Greg
谢谢Danh,我已经编辑了一些关于相关过去和未来接口的注释。 - Greg

15

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

有两种方法可以解决这个问题,一种是使用std::bind将 "指向成员函数的指针" 绑定到 this 指针。另一种方法是使用lambda捕获this指针并调用成员函数。

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++进行动态内存分配。

虽然我没有用其他编译器验证过,但我认为在其他编译器上也会得出类似的结果。


两种解决方案在我看来都不好。从代码量的角度来看,它们都太多了(我是指对于C++而言,不是你)。应该只需要这样:std::function<void(void)> g = doSomething; 来定义一个变量,对于一个以std函数作为参数的函数,应该这样定义:void MyFunction(std::function<void(void)> callback);,当我们调用它时,我们应该能够直接使用 MyFunction(doSomething); 完成,而不是将其包装在捕获this或std::bind的lambda中,如果没有提供其他对象,则应该默认为this(就像在函数中已经隐含的一样)。 - KulaGGin
当我们想要为成员函数提供不同的对象时,我们应该能够只需执行 MyFunction({someOtherObject, doSomething});。这比使用 std::bind 或捕获 this 的 lambda 更少输入,通常更少行数,并且更易于理解。 - KulaGGin

8
您可以避免使用std::bind进行如下操作:
std::function<void(void)> f = [this]-> {Foo::doSomething();}

1

如果您想要更精确的控制,可以使用函数对象(functors),这比通用方法更为有效。以下是使用我的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;
};

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

};

使用原则

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

祝好运,感谢所有分享知识的人。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接