通过函数指针调用 c + + 成员函数

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

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


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

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

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

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

357709 次浏览
typedef void (Dog::*memfun)();
memfun doSomething = &Dog::bark;
....
(pDog->*doSomething)(); // if pDog is a pointer
// (pDog.*doSomething)(); // if pDog is a reference

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

要调用一个成员函数,您需要知道两件事:

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

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

要创建一个新对象,您可以使用上面提到的“置入新对象”,或者让您的类实现创建对象副本的 clone ()方法。然后您可以使用上面解释的成员函数指针调用这个克隆方法来创建对象的新实例。克隆的优点是,有时您可能使用一个指向基类的指针,而您并不知道对象的类型。在这种情况下,克隆()方法更容易使用。另外,如果您想要的话,clone ()将允许您复制对象的状态。

详情请阅读 这个:

// 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');

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

typedef开始是最简单的。对于一个成员函数,在类型声明中添加类名:

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

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

(pDog->*pBark)();

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

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

我已经修改了您的代码,使其基本上可以完成您所描述的操作。

#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;
}

多亏了多态性的魔力,现在虽然你通常可以使用 Dog*来代替 Animal*,但是函数指针的类型确实遵循类层次结构的查找规则。所以 Animal 方法指针与 Dog 方法指针不兼容,换句话说,您不能将 Dog* (*)()赋给 Animal* (*)()类型的变量。

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

在回答了上面的问题之后,我确实想知道是否有更好的方法来实现你所需要的。在一些特定的场景中,您可能会做这类事情,但是您可能会发现其他模式对您的问题更有效。如果你用更一般的术语来描述你想要达到的目标,那么蜂群思维可能会被证明更加有用!

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

一个类成员的函数指针是一个非常适合于使用 ost: : 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);
}

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

函数的成员指针不仅仅是函数指针。从实现的角度来看,编译器不能使用简单的函数地址,因为一般来说,在知道对哪个对象解引用之前,您不知道要调用的地址(想想虚函数)。当然,您还需要知道对象,以便提供 this隐式参数。

之前说过你需要它们,现在我要说你真的需要避免它们。说真的,成员指针是个麻烦。更明智的做法是观察达到相同目标的面向对象设计模式,或者使用 boost::function或者上面提到的任何东西——假设你可以做出这样的选择。

如果你给现有的代码提供了这个函数指针,那么你真的需要一个简单的函数指针,你应该写一个函数作为类的静态成员。静态成员函数不理解 this,因此需要将对象作为显式参数传入。在这些代码行中曾经有一个并不罕见的习惯用法,用于处理需要函数指针的旧 C 代码

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


static void myfunction (myclass *p);
}


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

由于 myfunction实际上只是一个普通的函数(范围问题除外) ,所以可以用普通的 C 语言找到一个函数指针。

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

这种情况在使用旧的 Win32 API 时相当常见,它们最初是为 C 而不是 C + + 设计的。当然,在这种情况下,参数通常是 LPARAM 或类似的,而不是指针,因此需要进行一些强制转换。

最小可运行示例

Main.cpp

#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)

海湾合作委员会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“指向成员的运算符”解释了它们的作用

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

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...);
}
};

我用 std: : function 和 std: : bind 完成了这个操作。

我编写了这个 EventManager 类,它将处理程序的向量存储在一个 unorder _ map 中,该映射将事件类型(只是常数无符号整型,我有一个大的名称空间范围的枚举)映射到该事件类型的处理程序向量。

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

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;

然后回到 EventManagerTest: : 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);
}
);
}
}

这个管用。我在 EventManager 测试: : OnKeyDown 中接到电话。我必须删除的向量来清理时间,但一旦我这样做,有没有泄漏。在我的电脑上引发一个事件大约需要5微秒,大约是2008年。虽然不是很快,但是。很公平,只要我知道这一点,我不使用它在超热代码。

我想通过运行我自己的 std: : function 和 std: : bind 来加快速度,也许可以使用数组数组而不是无序的向量 _ map,但是我还没有完全弄明白如何存储一个成员函数指针,并且从对所调用的类一无所知的代码中调用它。睫毛的回答看起来很有趣。.