c++成员函数调用

C++ member function invocation

本文关键字:函数调用 成员 c++      更新时间:2023-10-16

好的,我经常看到下面这种类型的事件处理:

Connect(objectToUse, MyClass::MyMemberFunction);

用于某些类型的事件处理,其中objectToUse是MyClass类型。我的问题是这到底是怎么回事。如何将其转换成objectToUse->MyMemberFunction()

MyClass::MyMemberFunction是否从类的开始给出偏移量,然后可以用作函数指针?

除了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;
}

通常,这使用static成员函数(以指针作为参数),在这种情况下,objectToUse作为参数传递,MyMemberFunction将使用objectToUse建立指向MyClass对象的指针,并使用该指针来引用成员变量和成员函数。

在本例中,Connect将包含如下内容:

void Connect(void *objectToUse, void (*f)(void *obj))
{
     ...
     f(objectToUse);  
     ...
}

[也很有可能fobjectToUse被保存在某个地方供以后使用,而不是实际上在连接中,但在这种情况下调用看起来也一样-只是从作为该函数应该被调用的事件的结果而调用的其他函数]。

也可以使用指向成员函数的指针,但它相当复杂,而且根本不容易"正确"——无论是在语法方面还是"何时以及如何正确使用它"方面。

在这种情况下,Connect看起来像这样:

void Connect(MyClass *objectToUse, void (Myclass::*f)())
{
     ...
     objectToUse->*f();
     ...
}

很可能使用了模板,就像在Connect类中知道"MyClass"一样,使用函数指针是毫无意义的。虚函数是更好的选择。

在适当的情况下,也可以使用虚函数作为成员函数指针,但这需要编译器/环境"配合"。这里有一些关于这个主题的更多细节[我个人没有任何经验]:指向虚拟成员函数的指针。它是如何工作的?

Vlad还指出了Functors,这是一个包装函数的对象,允许将具有特定行为的对象作为"函数对象"传递进来。通常,这涉及一个预定义的成员函数或operatorXX,作为需要回调到代码中的函数处理的一部分调用。

c++ 11允许"Lambda函数",这是在代码中动态声明的函数,没有名称。这是我根本没用过的东西,所以我不能对此发表进一步的评论-我读过它,但没有必要在我的(爱好)编程中使用它-我的大部分工作生活都是用C,而不是c++,尽管我也用c++工作了5年。

我可能错了,但据我所知,

在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作为参数之一。

调用事件或回调的代码将直接使用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检索)链接到成员方法。