好的,我经常看到使用以下类型的事件处理:
Connect(objectToUse, MyClass::MyMemberFunction);
用于某种事件处理,其中objectToUse的类型为MyClass。我的问题是这是如何工作的。你会如何将它转换成objectToUse->MyMemberFunction()
MyClass::MyMemberFunction
是否从类的开头给出一个偏移量,然后可以用作函数指针?
答案 0 :(得分:2)
通常,这使用static
成员函数(将指针作为参数),在这种情况下,objectToUse
作为参数传入,MyMemberFunction
将使用objectToUse
设置指向MyClass
对象的指针,并使用它来引用成员变量和成员函数。
在这种情况下,Connect
将包含以下内容:
void Connect(void *objectToUse, void (*f)(void *obj))
{
...
f(objectToUse);
...
}
[很有可能f
和objectToUse
被保存在以后使用的地方,而不是实际在Connnect内部,但在这种情况下调用看起来也一样 - 只是从一些其他函数被调用为该函数应该被调用的事件的结果。
使用指向成员函数的指针也是可能的,但它非常复杂,并且根本不容易“正确” - 无论是语法还是“何时以及如何正确使用它”。查看更多here。
在这种情况下,Connect
看起来有点像这样:
void Connect(MyClass *objectToUse, void (Myclass::*f)())
{
...
objectToUse->*f();
...
}
很可能使用模板,就好像Connect类中已知“MyClass”一样,拥有一个函数指针是没有意义的。虚拟功能将是一个更好的选择。
在适当的情况下,您也可以使用虚函数作为成员函数指针,但它需要编译器/环境“一起玩”。以下是关于该主题的更多细节[我没有任何个人经验:指向虚拟成员函数的指针。它是如何工作的?
Vlad还指出Functors,它是一个包装函数的对象,允许具有特定行为的对象作为“函数对象”传递。通常,这涉及预定义的成员函数或operatorXX
,它被称为函数中处理的一部分,需要回调到代码中。
C ++ 11允许“Lambda函数”,它是代码中即时声明的函数,没有名称。这是我根本没用过的东西,所以我不能对此进行进一步的评论 - 我已经读过它了,但没有必要在我的(爱好)编程中使用它 - 我的大部分工作时间都是使用C而不是C ++,尽管我已经使用C ++工作了5年。
答案 1 :(得分:2)
除了Mats的回答之外,我还将举例说明如何在这类事物中使用非静态成员函数。如果您不熟悉指向成员函数的指针,可能需要先查看FAQ。
然后,考虑这个(相当简单)的例子:
class MyClass
{
public:
int Mult(int x)
{
return (x * x);
}
int Add(int x)
{
return (x + x);
}
};
int Invoke(MyClass *obj, int (MyClass::*f)(int), int x)
{ // invokes a member function of MyClass that accepts an int and returns an int
// on the object 'obj' and returns.
return obj->*f(x);
}
int main(int, char **)
{
MyClass x;
int nine = Invoke(&x, MyClass::Mult, 3);
int six = Invoke(&x, MyClass::Add, 3);
std::cout << "nine = " << nine << std::endl;
std::cout << "six = " << six << std::endl;
return 0;
}
答案 2 :(得分:1)
我可能在这里错了,但据我了解,
在C ++中,具有相同签名的函数是相同的。
具有n个参数的C ++成员函数实际上是具有n + 1个参数的普通函数。换句话说,void MyClass::Method( int i )
生效void (some type)function( MyClass *ptr, int i)
。
因此,我认为Connect在幕后工作的方式是将成员方法签名转换为普通的函数签名。它还需要一个指向实例的指针来实际连接工作,这就是为什么它需要objectToUse
换句话说,它本质上是使用指向函数的指针并将它们转换为更通用的类型,直到可以使用提供的参数和附加参数调用它,该附加参数是指向对象实例的指针
如果方法是静态的,那么指向实例的指针没有意义,它是直接类型转换。我还没有弄清楚非静态方法所涉及的复杂性 - 看看boost::bind的内部结构可能是你想要理解的:)这是静态函数的工作原理。
#include <iostream>
#include <string>
void sayhi( std::string const& str )
{
std::cout<<"function says hi "<<str<<"\n";
}
struct A
{
static void sayhi( std::string const& str )
{
std::cout<<"A says hi "<<str<<"\n";
}
};
int main()
{
typedef void (*funptr)(std::string const&);
funptr hello = sayhi;
hello("you"); //function says...
hello = (&A::sayhi); //This is how Connect would work with a static method
hello("you"); //A says...
return 0;
}
答案 3 :(得分:0)
对于事件处理或回调,它们通常采用两个参数 - 回调函数和userdata参数。回调函数的签名将userdata作为参数之一。
调用事件或回调的代码将直接使用userdata参数调用该函数。像这样的东西:
eventCallbackFunction(userData);
在您的事件处理或回调函数中,您可以选择使用userdata执行您想要的任何操作。
由于函数需要在没有对象的情况下直接调用,因此它可以是全局函数或类的静态方法(不需要对象指针)。
静态方法有一些限制,它只能访问静态成员变量并调用其他静态方法(因为它没有 this 指针)。这就是userData可用于获取对象指针的地方。
考虑到这一点,请查看以下示例代码段:
class MyClass
{
...
public:
static MyStaticMethod(void* userData)
{
// You can access only static members here
MyClass* myObj = (MyClass*)userdata;
myObj->MyMemberMethod();
}
void MyMemberMethod()
{
// Access any non-static members here as well
...
}
...
...
};
MyClass myObject;
Connect(myObject, MyClass::MyStaticMethod);
正如您所看到的,您可以访问成员变量和方法作为事件处理的一部分,如果您可以创建一个静态方法,该方法将首先使用对象指针(从userData检索)将调用链接到成员方法)。