2008-12-30 263 views
48

我正在使用一个API,要求我传递一个函数指针作为回调。我试图从我的班级使用这个API,但是我收到了编译错误。如何传递一个类成员函数作为回调?

以下是我从我的构造函数做的:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack); 

这并不编译 - 我得到以下错误:

Error 8 error C3867: 'CLoggersInfra::RedundencyManagerCallBack': function call missing argument list; use '&CLoggersInfra::RedundencyManagerCallBack' to create a pointer to member

我试过的建议,使用&CLoggersInfra::RedundencyManagerCallBack - 没有为我工作。

对此有何建议/解释?

我正在使用VS2008。

谢谢!

回答

38

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

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

,并调用初始化与

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

做是因为一个函数指针的静态成员函数是不是一个成员函数指针,因此可以像只是一个指针处理到一个免费的功能。

3

Init需要什么参数?什么是新的错误信息?

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

1

我可以看到,初始化具有以下控制装置:

初始化(CALLBACK_FUNC_EX callback_func,无效* callback_parm)

其中CALLBACK_FUNC_EX是 无效的typedef(* CALLBACK_FUNC_EX)(INT,无效*);

3

m_cRedundencyManager能够使用成员函数吗?大多数回调都设置为使用常规函数或静态成员函数。有关更多信息,请参阅C++ FAQ Lite中的this page

更新:您提供的函数声明显示m_cRedundencyManager期待函数的形式如下:void yourCallbackFunction(int, void *)。因此,在这种情况下,成员函数作为回调是不可接受的。一个静态成员函数可能工作,但如果您的情况不能接受,下面的代码也可以工作。请注意,它使用来自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); 
} 
2

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

如果你的API期望一个非成员回调函数,那就是你必须传递给它的东西。

0

question and answer来自C++ FAQ Lite涵盖了你的问题和我认为在答案中涉及的考虑相当好。我链接的网页短片段:

Don’t.

Because a member function is meaningless without an object to invoke it on, you can’t do this directly (if The X Window System was rewritten in C++, it would probably pass references to objects around, not just pointers to functions; naturally the objects would embody the required function and probably a whole lot more).

+0

该链接现在是https://isocpp.org/wiki/faq/pointers-to-members#memfnptr-vs-fnptr;看起来他现在说“不要”。这就是为什么只有链接的答案不好。 – 2016-01-12 19:45:11

+0

随意编辑我八岁的答案:-) – 2016-01-12 22:06:25

88

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

编译器无误建议您使用& CLoggersInfra :: RedundencyManagerCallBack。首先,如果RedundencyManagerCallBack是成员函数,则函数本身不属于类CLoggersInfra的任何特定实例。它属于类本身。如果你之前曾经调用过一个静态类函数,你可能会注意到你使用了相同的SomeClass :: SomeMemberFunction语法。由于函数本身是属于类而不是特定实例的“静态”,因此您使用相同的语法。 '&'是必要的,因为从技术上讲,你不直接传递函数 - 函数不是C++中的真实对象。而是在技术上传递函数的内存地址,即指向函数指令在内存中的起始位置的指针。但结果是一样的,你有效地'传递函数'作为参数。

但在这种情况下,这只是问题的一半。正如我所说的,RedundencyManagerCallBack函数不属于任何特定的实例。但是这听起来像是你想把它作为一个回调与一个特定的实例考虑在一起。要理解如何做到这一点,您需要了解成员函数的真正含义:常规未定义的任何类函数带有额外的隐藏参数。

例如:

 

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的需要?通常情况下,我们会说1.但引导下,foo真的需要2个。考虑A :: foo的定义,它需要一个特定的A实例,以使'this'指针有意义(编译器需要知道'这是)。通常,通过语法MyObject.MyMemberFunction()来指定要“this”的方式。但是这只是用于将MyObject的地址作为MyMemberFunction的第一个参数传递的语法糖。同样,当我们在类定义中声明成员函数时,我们不会将'this'放入参数列表中,但这仅仅是语言设计者为保存输入而提供的礼物。相反,你必须指定一个成员函数是静态的,以便自动退出它以获得额外的'this'参数。如果C++编译器编译上面的例子C代码(原来的C++编译器实际工作这种方式),它可能会写这样的事:

 

struct A { 
    int data; 
}; 

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

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

... 

A an_a_object; 
a_init(0); // Before constructor call was implicit 
a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5); 
 

回到你的榜样,现在有一个明显的问题。 'Init'需要一个指向一个参数的函数的指针。但是& CLoggersInfra :: RedundencyManagerCallBack是一个函数的指针,它接受两个参数,它是普通参数和秘密'this'参数。因此,为什么你仍然得到一个编译器错误(作为一个方面说明:如果你曾经使用Python,这种混淆是为什么所有成员函数都需要'self'参数)。

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

所以,现在,终于,很好的解决方案,提高::绑定和boost ::功能,每个的文档,你可以在这里找到:

boost::bind docsboost::function docs

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

当然,只有一个捕获。 boost :: bind将会为你提供一个很好的boost :: function,但是这在技术上仍然不像Init可能需要的原始函数指针。值得庆幸的是,boost提供了一种将boost :: function转换为raw指针的方法,如StackOverflow here中所记录。它如何实现这个超出了这个答案的范围,尽管它也很有趣。

不要担心,如果这看起来很可笑 - 你的问题与C++的几个较暗的角相交,而且boost :: bind一旦学习它就会非常有用。

4

此答案是对上述评论的回复,不适用于VisualStudio 2008,但应与更新的编译器一起使用。


同时您不必再使用一个空指针,也没有必要,因为提升和std::bindstd::function可用。一个优势(相比于空指针)是因为返回类型类型安全和参数使用std::function明确指出:

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

然后,您可以用std::bind创建函数指针,并把它传递给初始化:

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

Complete example使用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可以动态参数传递给回调(例如如果f有一个字符串参数,则可以在Init中使用return f("MyString");)。

相关问题