问题来源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
调用之上的} {}}。我只能想象这是将类实例定位在内存中然后是其中的函数。
答案 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);
}
我已修改它以考虑装配:
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
即。编译器能够完全消除蹦床。
答案 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
函数,以避免混乱。它不会执行成员函数所做的操作(如第二个示例中所示):它只调用成员函数。
不要担心调用过程中的一些指令。如果您确实需要担心时钟周期,请使用汇编程序。