133

如何获取类成员函数的函数指针,然后使用特定对象调用该成员函数?我想写:

class Dog : Animal
{
    Dog ();
    void bark ();
}

…
Dog* pDog = new Dog ();
BarkFunction pBark = &Dog::bark;
(*pBark) (pDog);
…

另外,如果可能的话,我还想通过指针调用构造函数:

NewAnimalFunction pNew = &Dog::Dog;
Animal* pAnimal = (*pNew)();    

这可能吗?如果可以,最好的方法是什么?

4

10 回答 10

149

详细阅读内容:

// 1 define a function pointer and initialize to NULL

int (TMyClass::*pt2ConstMember)(float, char, char) const = NULL;

// C++

class TMyClass
{
public:
   int DoIt(float a, char b, char c){ cout << "TMyClass::DoIt"<< endl; return a+b+c;};
   int DoMore(float a, char b, char c) const
         { cout << "TMyClass::DoMore" << endl; return a-b+c; };

   /* more of TMyClass */
};
pt2ConstMember = &TMyClass::DoIt; // note: <pt2Member> may also legally point to &DoMore

// Calling Function using Function Pointer

(*this.*pt2ConstMember)(12, 'a', 'b');
于 2009-09-28T10:04:27.077 回答
63

如何获取类成员函数的函数指针,然后使用特定对象调用该成员函数?

最简单的方法是从typedef. 对于成员函数,在类型声明中添加类名:

typedef void(Dog::*BarkFunction)(void);

然后要调用该方法,请使用->*运算符:

(pDog->*pBark)();

另外,如果可能的话,我也想通过指针调用构造函数。这可能吗?如果可以,最好的方法是什么?

我不相信你可以使用这样的构造函数——ctors 和 dtors 是特殊的。实现这种事情的正常方法是使用工厂方法,它基本上只是一个为您调用构造函数的静态函数。有关示例,请参见下面的代码。

我已经修改了您的代码,基本上按照您的描述进行。下面有一些注意事项。

#include <iostream>

class Animal
{
public:

    typedef Animal*(*NewAnimalFunction)(void);

    virtual void makeNoise()
    {
        std::cout << "M00f!" << std::endl;
    }
};

class Dog : public Animal
{
public:

    typedef void(Dog::*BarkFunction)(void);

    typedef Dog*(*NewDogFunction)(void);

    Dog () {}

    static Dog* newDog()
    {
        return new Dog;
    }

    virtual void makeNoise ()
    {
        std::cout << "Woof!" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    // Call member function via method pointer
    Dog* pDog = new Dog ();
    Dog::BarkFunction pBark = &Dog::makeNoise;

    (pDog->*pBark)();

    // Construct instance via factory method
    Dog::NewDogFunction pNew = &Dog::newDog;

    Animal* pAnimal = (*pNew)();

    pAnimal->makeNoise();

    return 0;
}

现在,虽然由于多态的魔力,您通常可以使用 aDog*代替 an Animal*,但函数指针的类型并不遵循类层次结构的查找规则。因此 Animal 方法指针与 Dog 方法指针不兼容,换句话说,您不能将 a 分配给Dog* (*)()type 的变量Animal* (*)()

静态newDog方法是工厂的一个简单示例,它简单地创建并返回新实例。作为一个静态函数,它有一个常规typedef(没有类限定符)。

回答了上述问题后,我想知道是否没有更好的方法来实现您所需要的。在一些特定的场景中你会做这种事情,但你可能会发现还有其他模式可以更好地解决你的问题。如果你用更笼统的术语描述你想要达到的目标,蜂巢思维可能会更有用!

与上述相关,您无疑会发现Boost 绑定库和其他相关模块非常有用。

于 2009-09-28T10:20:45.937 回答
33

我认为没有人在这里解释过一个问题是您需要“成员指针”而不是普通的函数指针。

指向函数的成员指针不仅仅是函数指针。在实现方面,编译器不能使用简单的函数地址,因为通常在知道要取消引用哪个对象(想想虚函数)之前,您不知道要调用的地址。当然,您还需要知道对象才能提供this隐式参数。

说了你需要它们,现在我要说你真的需要避免它们。说真的,成员指针很痛苦。看看实现相同目标的面向对象的设计模式,或者使用boost::function上面提到的 a 或其他任何东西——假设你可以做出那个选择,那就更理智了。

如果您将该函数指针提供给现有代码,那么您确实需要一个简单的函数指针,您应该编写一个函数作为类的静态成员。静态成员函数不理解this,因此您需要将对象作为显式参数传入。在处理需要函数指针的旧 C 代码时,曾经有一个不那么不寻常的习惯用法

class myclass
{
  public:
    virtual void myrealmethod () = 0;

    static void myfunction (myclass *p);
}

void myclass::myfunction (myclass *p)
{
  p->myrealmethod ();
}

由于myfunction实际上只是一个普通函数(范围问题除外),因此可以以普通 C 方式找到函数指针。

编辑- 这种方法称为“类方法”或“静态成员函数”。与非成员函数的主要区别在于,如果从类外部引用它,则必须使用::范围解析运算符指定范围。例如,要获取函数指针,请使用&myclass::myfunction并调用它使用myclass::myfunction (arg);

这种事情在使用旧的 Win32 API 时相当普遍,这些 API 最初是为 C 而不是 C++ 设计的。当然,在这种情况下,参数通常是 LPARAM 或类似的而不是指针,并且需要进行一些转换。

于 2009-09-28T14:54:11.217 回答
18
typedef void (Dog::*memfun)();
memfun doSomething = &Dog::bark;
....
(pDog->*doSomething)(); // if pDog is a pointer
// (pDog.*doSomething)(); // if pDog is a reference
于 2009-09-28T08:33:40.760 回答
15

最小可运行示例

主文件

#include <cassert>

class C {
    public:
        int i;
        C(int i) : i(i) {}
        int m(int j) { return this->i + j; }
};

int main() {
    // Get a method pointer.
    int (C::*p)(int) = &C::m;

    // Create a test object.
    C c(1);
    C *cp = &c;

    // Operator .*
    assert((c.*p)(2) == 3);

    // Operator ->*
    assert((cp->*p)(2) == 3);
}

编译并运行:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

在 Ubuntu 18.04 中测试。

您不能更改括号的顺序或省略它们。以下不起作用:

c.*p(2)
c.*(p)(2)

GCC 9.2 会失败:

main.cpp: In function ‘int main()’:
main.cpp:19:18: error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in ‘p (...)’, e.g. ‘(... ->* p) (...)’
   19 |     assert(c.*p(2) == 3);
      |

C++11 标准

.*并且->*是为此目的在 C++ 中引入的单个运算符,在 C 中不存在。

C++11 N3337 标准草案

  • 2.13 “运算符和标点符号”有一个所有运算符的列表,其中包含.*->*
  • 5.5 “指向成员的操作符”解释它们的作用
于 2015-07-01T09:06:17.943 回答
12

我来这里是为了学习如何从方法创建函数指针(不是方法指针),但这里的答案都没有提供解决方案。这是我想出的:

template <class T> struct MethodHelper;
template <class C, class Ret, class... Args> struct MethodHelper<Ret (C::*)(Args...)> {
    using T = Ret (C::*)(Args...);
    template <T m> static Ret call(C* object, Args... args) {
        return (object->*m)(args...);
    }
};

#define METHOD_FP(m) MethodHelper<decltype(m)>::call<m>

因此,对于您的示例,您现在将执行以下操作:

Dog dog;
using BarkFunction = void (*)(Dog*);
BarkFunction bark = METHOD_FP(&Dog::bark);
(*bark)(&dog); // or simply bark(&dog)

编辑:
使用 C++17,有一个更好的解决方案:

template <auto m> struct MethodHelper;
template <class C, class Ret, class... Args, Ret (C::*m)(Args...)> struct MethodHelper<m> {
    static Ret call(C* object, Args... args) {
        return (object->*m)(args...);
    }
};

无需宏即可直接使用:

Dog dog;
using BarkFunction = void (*)(Dog*);
BarkFunction bark = MethodHelper<&Dog::bark>::call;
(*bark)(&dog); // or simply bark(&dog)

对于带有修饰符的方法,const您可能需要更多的专业化,例如:

template <class C, class Ret, class... Args, Ret (C::*m)(Args...) const> struct MethodHelper<m> {
    static Ret call(const C* object, Args... args) {
        return (object->*m)(args...);
    }
};
于 2016-06-18T10:15:48.933 回答
7

指向类成员的函数指针是一个非常适合使用 boost::function 的问题。小例子:

#include <boost/function.hpp>
#include <iostream>

class Dog 
{
public:
   Dog (int i) : tmp(i) {}
   void bark ()
   {
      std::cout << "woof: " << tmp << std::endl;
   }
private:
   int tmp;
};



int main()
{
   Dog* pDog1 = new Dog (1);
   Dog* pDog2 = new Dog (2);

   //BarkFunction pBark = &Dog::bark;
   boost::function<void (Dog*)> f1 = &Dog::bark;

   f1(pDog1);
   f1(pDog2);
}
于 2009-09-28T13:18:36.990 回答
6

不能使用函数指针调用成员函数的原因是普通函数指针通常只是函数的内存地址。

要调用成员函数,你需要知道两件事:

  • 调用哪个成员函数
  • 应该使用哪个实例(其成员函数)

普通函数指针不能同时存储两者。C++ 成员函数指针用于存储 a),这就是为什么在调用成员函数指针时需要显式指定实例的原因。

于 2009-09-28T08:55:27.510 回答
2

要创建一个新对象,您可以使用placement new,如上所述,或者让您的类实现一个创建对象副本的clone() 方法。然后,您可以使用如上所述的成员函数指针调用此克隆方法来创建对象的新实例。克隆的优点是有时您可能会使用指向不知道对象类型的基类的指针。在这种情况下,clone() 方法可以更容易使用。此外,如果这是您想要的,clone() 将允许您复制对象的状态。

于 2009-09-28T09:34:06.373 回答
2

我用 std::function 和 std::bind..

我编写了这个 EventManager 类,它将一个处理程序向量存储在一个 unordered_map 中,该映射将事件类型(它只是 const unsigned int,我有一个很大的命名空间范围的枚举)映射到该事件类型的处理程序向量。

在我的 EventManagerTests 类中,我设置了一个事件处理程序,如下所示:

auto delegate = std::bind(&EventManagerTests::OnKeyDown, this, std::placeholders::_1);
event_manager.AddEventListener(kEventKeyDown, delegate);

这是 AddEventListener 函数:

std::vector<EventHandler>::iterator EventManager::AddEventListener(EventType _event_type, EventHandler _handler)
{
    if (listeners_.count(_event_type) == 0) 
    {
        listeners_.emplace(_event_type, new std::vector<EventHandler>());
    }
    std::vector<EventHandler>::iterator it = listeners_[_event_type]->end();
    listeners_[_event_type]->push_back(_handler);       
    return it;
}

这是 EventHandler 类型定义:

typedef std::function<void(Event *)> EventHandler;

然后回到 EventManagerTests::RaiseEvent,我这样做:

Engine::KeyDownEvent event(39);
event_manager.RaiseEvent(1, (Engine::Event*) & event);

这是 EventManager::RaiseEvent 的代码:

void EventManager::RaiseEvent(EventType _event_type, Event * _event)
{
    if (listeners_.count(_event_type) > 0)
    {
        std::vector<EventHandler> * vec = listeners_[_event_type];
        std::for_each(
            begin(*vec), 
            end(*vec), 
            [_event](EventHandler handler) mutable 
            {
                (handler)(_event);
            }
        );
    }
}

这行得通。我在 EventManagerTests::OnKeyDown 中接到电话。我必须删除向量来清理时间,但是一旦我这样做了,就没有泄漏。在我的计算机上引发一个事件大约需要 5 微秒,大约是 2008 年。不是超级快,但是。只要我知道这一点并且我不在超热代码中使用它就足够公平了。

我想通过滚动我自己的 std::function 和 std::bind 来加速它,也许使用数组数组而不是向量的 unordered_map,但我还没有完全弄清楚如何存储成员函数指针并从对所调用的类一无所知的代码中调用它。睫毛的答案看起来很有趣..

于 2020-06-29T20:20:46.990 回答