我怎样才能传递一个类的成员函数作为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::bind
和std::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,可能还有更多)。