好吧,我经常看到使用以下类型的事件处理:
Connect(objectToUse, MyClass::MyMemberFunction);
对于 objectToUse 类型为 MyClass 的某种事件处理。我的问题是这到底是如何工作的。您如何将其转换为可以执行的操作objectToUse->MyMemberFunction()
是否MyClass::MyMemberFunction
给出了类开头的偏移量,然后可以用作函数指针?
通常,这使用一个static
成员函数(将指针作为参数),在这种情况下objectToUse
,作为参数传入,并且MyMemberFunction
将用于objectToUse
设置指向MyClass
对象的指针并使用它来引用成员变量和成员函数。
在这种情况下Connect
,将包含如下内容:
void Connect(void *objectToUse, void (*f)(void *obj))
{
...
f(objectToUse);
...
}
[也很有可能,f
并且objectToUse
保存在某个地方以供以后使用,而不是实际在 Connnect 内部,但在这种情况下调用看起来也一样 - 只是来自由于该函数的事件而调用的某个其他函数应该被要求]。
也可以使用指向成员函数的指针,但它非常复杂,而且“正确”一点也不容易——无论是在语法方面,还是在“何时以及如何正确使用它”方面。在这里查看更多。
在这种情况下,Connect
看起来有点像这样:
void Connect(MyClass *objectToUse, void (Myclass::*f)())
{
...
objectToUse->*f();
...
}
很有可能使用了模板,好像“MyClass”在 Connect 类中是已知的,那么拥有一个函数指针将毫无意义。虚函数将是更好的选择。
在适当的情况下,您也可以将虚函数用作成员函数指针,但它需要编译器/环境“配合”。这是关于该主题的更多详细信息[我完全没有个人经验:指向虚拟成员函数的指针。它是如何工作的?
Vlad 还指出了 Functors,它是一个包装函数的对象,允许将具有特定行为的对象作为“函数对象”传入。通常,这涉及到预定义的成员函数或operatorXX
在需要回调到代码中的函数中作为处理的一部分调用的成员函数。
C++11 允许“Lambda 函数”,这是在代码中动态声明的函数,没有名称。这是我根本没有使用过的东西,所以我无法对此发表进一步评论 - 我已经阅读过它,但不需要在我的(爱好)编程中使用它 - 我的大部分工作生活是使用 C 而不是 C++,尽管我也使用 C++ 工作了 5 年。
除了 Mats 的回答之外,我还会给您一个简短的示例,说明如何在此类事物中使用非静态成员函数。如果您不熟悉指向成员函数的指针,您可能需要先查看常见问题解答。
然后,考虑这个(相当简单的)示例:
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;
}
我可能在这里错了,但据我所知,
在 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;
}
对于事件处理或回调,它们通常采用两个参数——一个回调函数和一个用户数据参数。回调函数的签名将 userdata 作为参数之一。
调用事件或回调的代码将直接使用 userdata 参数调用函数。例如这样的事情:
eventCallbackFunction(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 检索)链接到成员方法的调用,您甚至可以访问成员变量和方法作为事件处理的一部分。