如何使用std :: function编写指向成员函数的指针?

34
我知道如何在std :: function (std :: function<int(double)>)中声明int fn(double)。我知道如何编写指向成员函数的指针 (typedef int (A :: * MemFn)(double d);)。但是,如何使用std :: function编写指向成员函数的指针?

如果您想编译/测试,请参考下面的虚拟代码

-根据答案,我认为我将只使用typedef,不再烦恼std :: function

#include <cstdio>
#include <functional>

struct A{ int fn(double){ return 0; } };
int fn2(double){ return 0; }

typedef int (A::*MemFn)(double d);
typedef std::function<int(double)> MemFn2;

void Test(A*a, MemFn2 fn){
    fn(1.2f);
}
void Test(A*a, MemFn fn){
    (a->*fn)(1.2f);
}

int main(){
    Test(new A, &A::fn);
    Test(new A, &fn2);
}
5个回答

30

std::function 可以直接存储成员函数指针。但是,您必须适当地调整参数列表。成员指针必须使用类型的实例(或派生类型)进行调用。将它们放入 std::function 中时,需要在参数列表中第一个参数处提供指向该对象类型的指针(或引用或智能指针)。

因此,如果我有以下类:

struct Type
{
public:
    int Foo();
};

将这个成员函数存储在std::function中的正确语法是:

std::function<int(Type&)> fooCaller = &Type::Foo;
如果您想保留参数列表(在您的情况下,是int(double)),则需要在function之外提供实例。这可以通过std::bind来实现:
struct A{ int fn(double){ return 0; } };

A anInstance;
std::function<int(double)> fnCaller = std::bind(&A::fn, &anInstance, std::placeholders::_1);

请注意,std::bind所提供的对象指针应该在 fnCaller 存活期间一直保持有效。确保传入的对象指针仍然存活是你的责任。如果你将 fnCaller 返回给其他人,并且它带有指向堆栈对象的指针,则会出现问题。

美妙的是,由于函数调用机制的定义方式,你可以将一个shared_ptr (或任何可复制的智能指针)绑定为对象:

struct A{ int fn(double){ return 0; } };

auto anInstance = std::make_shared<A>();
std::function<int(double)> fnCaller = std::bind(&A::fn, anInstance, std::placeholders::_1);

现在你不必担心了;因为绑定器会通过值存储 shared_ptr,所以它将继续保持对象的生命。


这样正确吗?我认为 std::function<int(Type&)> 能够存储这种类型的自由函数:typedef int (*FunType)(Type&),即一个接受 Type& 类型参数并返回 int 的函数。 - Nawaz
3
@Nawaz:你尝试在编译器中运行过吗?它可以工作。 规范说明它可以工作。我无法引用规范,因为这些内容的定义分散在第20.8节的大约4个部分中,但请注意20.8.2和20.8.11。 - Nicol Bolas
1
我完全惊讶了。: |。+1 - Nawaz
看起来是正确的,所以我接受了,但是typedef int (A::*MemFn)(double d);看起来更容易;)。我并不是很关心对象,只是如何指定函数指针。 - user34537
1
请参阅 [func.require] 部分以获取 std::function 使用的“可调用”定义。std::function 绝对可以存储和调用指向成员函数的指针。std::function 对象的第一个参数是适当类型的对象或指向这样的对象的指针。 - bames53
显示剩余6条评论

10

成员函数并不是一个函数。它本身不能被调用。你只能调用实例对象的成员函数。只有指向成员函数和对象的指针对才构成可调用实体。

要将实例绑定到指向成员函数的指针上并获得可调用的内容,使用bind

#include <functional>

struct Foo
{
    double bar(bool, char);
};

Foo x;
using namespace std::placeholders;
std::function<double(bool, char)> f = std::bind(&Foo::bar, x, _1, _2);
f(true, 'a'); //...

与Lambda表达式一样,绑定表达式的类型是无法预知的,转换为std::function(以及实际分派)可能会很昂贵。如果可能的话,最好使用auto来声明绑定表达式的类型。


阅读规范(func.bind.bind)后,我认为std::bindboost::bind不同之处在于它生成一个可调用对象,该对象接受指定数量的参数,需要使用占位符。因此,你需要说bind(&Foo::bar,x,_1,_2),而且使用不同数量的参数调用可能是未定义的。模拟可变模板可能无法实现这一点。 - bames53
@bames53:你说得对,可能应该加上额外的参数。我有一种奇怪的感觉,认为它们会被隐式添加,但这可能指的是boost版本。无论如何,让我编辑一下。 - Kerrek SB
不错的解决方案,但应该是 using namespace std::placeholders; - Gene Vincent
@GeneVincent:谢谢,已修复! - Kerrek SB

7

Scott Meyer在他的《现代C++11》一书中提出的一个指南是避免使用std::bind,而始终使用lambda闭包:

struct A{ int fn(double){ return 0; } };

std::function<int(double)> f = [a = A{}](double x) mutable { return a.fn(x); };

这里需要用到mutable,因为捕获的a可能会在函数调用中被改变(因为A::fn是非常量)。

这种方式对我来说更加清晰。你知道为什么Meyer建议这样做吗? - JeffCharter
@JeffCarter:很可能因为这确实更加清晰。 - davidhigh

0
您可以使用std::binder1st将成员函数绑定到类实例:
typedef std::binder1st<std::mem_fun1_t<int, A, double>> MemFn;

void Test(A* a, double d)
{
   MemFn fn(std::mem_fun(&A::fn), a);
   int nRetVal = fn(d);
}

int main()
{
   Test(new A, 1.2f);
   return 0;
}

0
如果您可以使用 Boost,那么您也可以使用 Boost.Bind。这很容易实现,只需按照以下方式操作即可:
boost::bind(&MyClass::MemberFunction, pInstance, _1, _2)

希望这很容易理解。 _1_2 是可以传递给函数的参数占位符。

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