如何将成员函数作为函数指针传递给函数?

时间:2016-08-02 14:59:53

标签: c++ member-function-pointers

问题来源https://github.com/claydonkey/PointerToMember/tree/master

虽然在How Can I Pass a Member Function to a Function Pointer?中提及,但我对所提供的解决方案感到有些不满,因为我不想引入对Boost库的依赖。

Comparing std::function for member functions是一个接近解决方案的帖子,但最终对使用 std :: function 不太乐观。 (似乎成员函数不能作为函数指针传递)

问题:

无法更改的功能simpleFunction需要回调 pfunc

typedef int (*FuncPtr_t)(void*, std::pair<int,int>&);
static int simpleFunction(FuncPtr_t pfunc, void *context, std::pair<int,int>& nos)
{
    pfunc(context, nos);
}

此函数用于回调类memberFunction中的方法SimpleClass

NB从原始帖子中移除了,因为它更好地代表了真实世界的用法。*是int memberFunction(void*, std::pair<int,int>& nos)

class SimpleClass {
public:  
    int memberFunction(std::pair<int,int>& nos) { return nos.first + nos.second; }
};

我期望以下工作:

MemFuncPtr_t MemFunction = &SimpleClass::memberFunction;
simpleFunction(obj.*MemFunction, nos);

obj.*MemFunction的类型为:int (SimpleClass::)(std::pair<int,int>&)

,它必须是:int (*)(std::pair<int,int>&)

(whe (obj.*MemFunction) (nos);按预期返回)

我可以创建并传递蹦床:

int functionToMemberFunction(void* context, std::pair<int,int> & nos) {
     return static_cast<SimpleClass*>(context)->memberFunction(nos);
}

并传递

simpleFunction(&functionToMemberFunction, &obj, nos);

但它编写了大约40条指令。

我可以传递一个lambda:

simpleFunction((FuncPtr_t)[](void* , std::pair<int,int> & nos) {
    return nos.first + nos.second;
}, &obj, nos);

令人惊讶地优化了但有点丑陋和语法上的麻烦。 (NB Both和lambdas需要C ++ 11)

我可以将静态成员添加到SimpleClass

class SimpleClass {
public:  
    int memberFunction(void*, std::pair<int,int>& nos) { return nos.first + nos.second; }
    static int staticFunction(void*, std::pair<int,int> & nos) { return nos.first + nos.second; }
};

FuncPtr_t StaticMemFunction = &SimpleClass::staticFunction;

并传递

simpleFunction(StaticMemFunction, nullptr, nos);

而且那只是......一个类中的静态函数。

我可以使用<functional>标题:

using namespace std::placeholders;

std::function<int(std::pair<int,int>&) > f_simpleFunc = 
std::bind(&SimpleClass::memberFunction, obj, _1);

auto ptr_fun = f_simpleFunc.target<int (std::pair<int,int> & ) >();

并尝试通过它......

simpleFunction(*ptr_fun, nos);

但是ptr_fun报告为null。

查看x86程序集 - 我对如何处理内存感到茫然,调用成员函数(还有5条指令[3 mov,1 lea和1 {{1} add调用之上的} {}}。我只能想象这是将类实例定位在内存中然后是其中的函数。

5 个答案:

答案 0 :(得分:2)

所有的建议都很有用,我想如果我把它们整理好并回到原来的问题,我可能会找到适合我的解决方案。

所以我认为解决方案可以来自:

simpleFunction(([](void* context,std::pair<int, int> & nos) {
   return nos.first + nos.second; 
}), &obj,  nos);

成为:

simpleFunction(([&](void* context,std::pair<int, int> & nos) {
   obj.memberFunction(nos);
}), &obj,  nos);

正确?

错误:无法转换main()::<lambda(std::pair<int, int>&, void*)> to int (*)(std::pair<int, int>&, void*)

接受闭包的Lambda无法转换为函数指针

  

没有lambda-capture 的lambda表达式的闭包类型有一个   公共非虚拟非显式const 转换函数到指针   使用具有与闭包相同的参数和返回类型   type的函数调用运算符。此转换返回的值   function应该是一个函数的地址,当被调用时,它具有   与调用闭包类型的函数调用操作符的效果相同。

这是有道理的,因为函数指针不带状态,这就是为什么simpleFunction被赋予了一个上下文指针void* context(就像大多数回调一样!),后者由pFunc处理。 - 函数指针。 (上下文是我们希望委托给其成员函数的SimpleObject实例obj

Ergo一个好的解决方案似乎是:

解决方案1 ​​

simpleFunction(([](void* context, std::pair<int,int>& n) {
    return static_cast<SimpleClass*>(context)->memberFunction(n);
}), &obj, nos);

NB如果从本地移动obj - &gt;全局范围lambda根本不需要传入对象。但这改变了原来的问题。

令人难以置信的是,如果member-function没有调用它所在的类,它就像一个静态函数,lambda不需要类实例

解决方案2

simpleFunction(([](void* context, std::pair<int,int>& n) {
    return static_cast<SimpleClass*>(context)->memberFunction(n);
}), nullptr /* << HERE */, nos); //WILL WORK even though the context is null!

这完全可以作为原始问题的解决方案:成员函数确实不依赖于函数范围之外的任何东西(这是预期的C ++行为还是快乐的黑客?)。

总之,在尝试对现实世界问题进行简单类比时,我在原始问题中一直很天真,我真的想要成员 -function的所有功能,所以解决方案1似乎更现实。 我在区分成员函数和c函数方面更加精明 - 我把线索放在名字成员(类的)中

这是学习体验的一部分,包含移动语义解决方案的源代码位于原帖的链接中。

答案 1 :(得分:1)

使用lambda:

实现一个简单的蹦床
#include <iostream>

typedef int (*FuncPtr_t)(void*, int);
static int simpleFunction(FuncPtr_t pfunc, void *context, int nos)
{
    return pfunc(context, nos);
}

struct A {
    int i;
    int pf(int nos) { std::cout << i << " nos = " << nos << "\n"; return i; }
};

int main() {
    A a { 1234 };
    // could combine the next two lines into one, I didn't.
    auto trampoline = [](void *inst, int nos) { return ((A*)inst)->pf(nos); };
    simpleFunction(trampoline, &a, 42);
}

http://ideone.com/74Xhes

我已修改它以考虑装配:

typedef int (*FuncPtr_t)(void*, int);
static int simpleFunction(FuncPtr_t pfunc, void *context, int nos)
{
    return pfunc(context, nos);
}

struct A {
    int i;
    int pf(int nos) { return nos + i; }
};

int f(A& a) {
    auto trampoline = [](void *inst, int nos) { return ((A*)inst)->pf(nos); };
    return simpleFunction(trampoline, &a, 42);
}

使用-O3编译,我们得到:

f(A&):
    movl    (%rdi), %eax
    addl    $42, %eax
    ret

https://godbolt.org/g/amDKu6

即。编译器能够完全消除蹦床。

答案 2 :(得分:1)

std::function<>lambdas是一个很好的方式。只需捕获lambda中的this即可。如果正在执行的内容很小,则不需要编写单独的回调。对于仅捕获单个指针的lambda,需要加std::function不需要堆分配。

class A {
  std::function <void()> notify;
  void someProcessingFunction () {
    // do some work
    if (notify != nullptr)
      notify ();
  }
};

class B {
  void processNotification () {
    // do something in response to notification
  }
};

int main ()
{
  A a;
  B b;

  a.notify = [&b] () { b.processNotification (); };

  a.someProcessingFunction ();
}

答案 3 :(得分:0)

通常的方法是将对象作为回调数据传递,就像在第一个示例中那样。任何开销都可能是您的目标上的调用约定的结果(或者编译器的优化器上的设置可能太低)。

答案 4 :(得分:0)

在这些情况下,我使用前两种方法的融合。也就是说,我创建了一个蹦床,但在类中使它成为static函数,以避免混乱。它不会执行成员函数所做的操作(如第二个示例中所示):它只调用成员函数。

不要担心调用过程中的一些指令。如果您确实需要担心时钟周期,请使用汇编程序。