我怎样才能传递一个类的成员函数作为callback?

我正在使用一个API,要求我传递一个函数指针作为callback。 我试图从我的class级使用这个API,但我得到编译错误。

这是我从我的构造函数做的:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack); 

这不会编译 – 我得到以下错误:

错误8错误C3867:'CLoggersInfra :: RedundencyManagerCallBack':函数调用缺less参数列表; 使用&CLoggersInfra :: RedundencyManagerCallBack创build一个指向成员的指针

我试着build议使用&CLoggersInfra::RedundencyManagerCallBack – 没有为我工作。

对此有何build议/解释?

我正在使用VS2008。

谢谢!!

正如你现在显示你的init函数接受一个非成员函数。 所以这样做:

 static void Callback(int other_arg, void * this_pointer) { CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer); self->RedundencyManagerCallBack(other_arg); } 

并用Init调用

 m_cRedundencyManager->Init(&CLoggersInfra::Callback, this); 

这是有效的,因为指向静态成员函数的函数指针不是成员函数指针,因此可以像只是一个指向自由函数的指针那样处理。

这是一个简单的问题,但答案是惊人的复杂。 简短的答案是你可以做你正在做的事情与std :: bind1st或boost :: bind。 较长的答案在下面。

编译器build议您使用&CLoggersInfra :: RedundencyManagerCallBack是正确的。 首先,如果RedundencyManagerCallBack是成员函数,则函数本身不属于类CLoggersInfra的任何特定实例。 它属于类本身。 如果你以前曾经调用过一个静态类函数,你可能会注意到你使用了相同的SomeClass :: SomeMemberFunction语法。 由于函数本身是属于类而不是特定实例的“静态”,因此使用相同的语法。 '&'是必要的,因为从技术上说,你不直接传递函数 – 函数不是C ++中的真实对象。 相反,你在技术上传递函数的内存地址,也就是指向函数指令在内存中开始的地方。 结果是一样的,虽然,你有效地“传递一个函数”作为参数。

但这只是这个问题的一半。 正如我所说的,RedundencyManagerCallBack函数不属于任何特定的实例。 但是这听起来像是想把它作为一个callback来传递一个特定的实例。 要理解如何做到这一点,你需要了解什么成员函数真的是:规则未定义在任何类的函数与一个额外的隐藏参数。

例如:

class A { public: A() : data(0) {} void foo(int addToData) { this->data += addToData; } int data; }; ... A an_a_object; an_a_object.foo(5); A::foo(&an_a_object, 5); // This is the same as the line above! std::cout 

A :: foo有多less个参数? 通常情况下,我们会说1.但是在底层,foo真的需要2个。看看A :: foo的定义,它需要一个特定的A实例,以使'this'指针有意义(编译器需要知道什么是'这是)。 通常,通过语法MyObject.MyMemberFunction()来指定要“this”的方式。 但是这只是用于将MyObject的地址作为MyMemberFunction的第一个parameter passing的语法糖。 同样,当我们在类定义中声明成员函数时,我们不会把“this”放在参数列表中,但这只是语言devise者为了保存input而提供的一个礼物。 相反,你必须指定一个成员函数是静态的,select退出它自动得到额外的'this'参数。 如果C ++编译器将上面的例子翻译成C代码(原来的C ++编译器实际上是以这种方式工作的话),那么可能会这样写:


 struct A {
     int数据;
 };

 void a_init(A * to_init)
 {
     to_init-> data = 0;
 }

 void a_foo(A * this,int addToData)
 { 
     this-> data + = addToData;
 }

 ...

 an_a_object;
 A_INIT(0);  //构造函数调用是隐式的
 a_foo(&an_a_object,5);  //曾经是an_a_object.foo(5);

回到你的例子,现在有一个明显的问题。 'Init'需要一个指向一个参数的函数的指针。 但是&CLoggersInfra :: RedundencyManagerCallBack是一个指向一个函数的指针,它带有两个参数,它是普通参数和秘密“this”参数。 因此,为什么你仍然得到一个编译器错误(作为一个旁注:如果你曾经使用Python,这种混淆是为什么所有成员函数需要一个“自我”参数)。

处理这个问题的详细方法是创build一个特殊的对象,该对象包含一个指向你想要的实例的指针,并且有一个叫做“run”或者“execute”的成员函数(或者重载'()'操作符)为成员函数,并简单地使用存储的实例上的这些参数调用成员函数。 但是这需要你改变'Init'来取得你的特殊对象,而不是一个原始的函数指针,而且这听起来像是Init是别人的代码。 每次出现这个问题时都要做一个特殊的类,这会导致代码的膨胀。

所以现在,最后,好的解决scheme,boost :: bind和boost :: function,你可以在这里find每个文档的文档:

boost :: bind docs , boost :: function docs

boost :: bind可以让你把一个函数和一个parameter passing给这个函数,并在这个参数被locking的地方创build一个新的函数。 所以如果我有一个函数添加两个整数,我可以使用boost :: bind来创build一个新的函数,其中一个参数被locking为5.这个新函数只会带一个整数参数,并且总是会添加5个到它。 使用这种技术,您可以将隐藏的this参数“locking”为特定的类实例,并生成一个只需要一个参数的新函数,就像您想要的那样(注意,隐藏参数总是第一个参数,正常的参数按顺序排列)。 看看boost :: bind docs的例子,他们甚至专门讨论了如何使用它的成员函数。 从技术上讲,有一个标准函数叫做std :: bind1st,你也可以使用它,但是boost :: bind更通用。

当然,还有一个问题。 boost :: bind会为你创build一个很好的boost :: function,但是这在技术上还不是像Init那样需要的原始函数指针。 值得庆幸的是,boost提供了一种将boost :: function转换为raw指针的方法,就像StackOverflow中logging的那样。 它如何实现这个超出了这个答案的范围,尽pipe它也很有趣。

不要担心,如果这似乎很难 – 你的问题相交C ++的几个黑暗的angular落,一旦你学习它,boost :: bind是非常有用的。

Init采取什么论据? 什么是新的错误信息?

C ++中的方法指针有点难以使用。 除了方法指针本身,你还需要提供一个实例指针(在你的情况下)。 也许Init期望它作为一个单独的论点?

m_cRedundencyManager是否可以使用成员函数? 大多数callback被设置为使用常规函数或静态成员函数。 有关更多信息,请参阅C ++ FAQ Lite的此页面 。

更新:您提供的函数声明显示m_cRedundencyManager正在期待函数的forms: void yourCallbackFunction(int, void *) 。 因此,在这种情况下,成员函数作为callback是不可接受的。 一个静态成员函数可能工作,但如果这是不可接受的,下面的代码也可以工作。 请注意,它使用来自void *的邪恶强制转换。

 // in your CLoggersInfra constructor: m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this); 
 // in your CLoggersInfra header: void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr); 
 // in your CLoggersInfra source file: void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr) { ((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i); } 

这个答案是对上述评论的回复,不适用于VisualStudio 2008,但应该是更新的编译器。


同时,你不必再使用void指针了,因为std::bindstd::function是可用的,所以也不需要提升。 一个优点(与void指针相比)是types安全的,因为返回types和参数是使用std::function明确声明的:

 // std::function<return_type(list of argument_type(s))> void Init(std::function<void(void)> f); 

然后你可以用std::bind创build函数指针,并把它传递给Init:

 auto cLoggersInfraInstance = CLoggersInfra(); auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance); Init(callback); 

使用成员,静态成员和非成员函数使用std::bind 完整示例 :

 #include <functional> #include <iostream> #include <string> class RedundencyManager // incl. Typo ;-) { public: // std::function<return_type(list of argument_type(s))> std::string Init(std::function<std::string(void)> f) { return f(); } }; class CLoggersInfra { private: std::string member = "Hello from non static member callback!"; public: static std::string RedundencyManagerCallBack() { return "Hello from static member callback!"; } std::string NonStaticRedundencyManagerCallBack() { return member; } }; std::string NonMemberCallBack() { return "Hello from non member function!"; } int main() { auto instance = RedundencyManager(); auto callback1 = std::bind(&NonMemberCallBack); std::cout << instance.Init(callback1) << "\n"; // Similar to non member function. auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack); std::cout << instance.Init(callback2) << "\n"; // Class instance is passed to std::bind as second argument. // (heed that I call the constructor of CLoggersInfra) auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack, CLoggersInfra()); std::cout << instance.Init(callback3) << "\n"; } 

可能的输出:

 Hello from non member function! Hello from static member callback! Hello from non static member callback! 

此外,使用std::placeholders你可以dynamic地传递参数到callback(例如,这使得return f("MyString");Init如果f有一个string参数)。

指向类成员函数的指针与指向函数的指针不同。 一个类成员需要一个隐含的额外参数( this指针),并使用不同的调用约定。

如果你的API期望一个非成员callback函数,那么你必须传递给它。

我可以看到init有以下的覆盖:

初始化(CALLBACK_FUNC_EX callback_func,void * callback_parm)

CALLBACK_FUNC_EX是typedef void(* CALLBACK_FUNC_EX)(int,void *);

来自C ++ FAQ Lite的这个问题和答案涵盖了你的问题,以及我认为在答案中涉及的考虑。 我链接的网页短片段:

别。

因为一个成员函数在没有对象的情况下是没有意义的,所以你不能直接这样做(如果X窗口系统用C ++重写,它可能会传递对象的引用,而不仅仅是指向函数的指针;会体现所需的function,可能还有更多)。