如何获取类成员函数的函数指针,稍后使用特定对象调用该成员函数?我想写:
class Dog : Animal
{
Dog ();
void bark ();
}
…
Dog* pDog = new Dog ();
BarkFunction pBark = &Dog::bark;
(*pBark) (pDog);
…
另外,如果可能的话,我也想通过指针调用构造函数:
NewAnimalFunction pNew = &Dog::Dog;
Animal* pAnimal = (*pNew)();
这可能吗?如果可行,那么首选方法是什么?
答案 0 :(得分:106)
阅读this了解详细信息:
// 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');
答案 1 :(得分:52)
如何获取类成员函数的函数指针,稍后用特定对象调用该成员函数?
最简单的方法是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;
}
现在虽然由于多态性的魔力,您通常可以在Dog*
的位置使用Animal*
,但是函数指针的类型不跟随查找类层次结构的规则。因此,Animal方法指针与Dog方法指针不兼容,换句话说,您无法将Dog* (*)()
分配给Animal* (*)()
类型的变量。
静态newDog
方法是工厂的一个简单示例,它只是创建并返回新实例。作为一个静态函数,它有一个常规typedef
(没有类限定符)。
在回答了上述问题后,我想知道是否有更好的方法来实现您的需求。有一些特定的场景,你会做这种事情,但你可能会发现其他模式更适合你的问题。如果你用更一般的术语描述你想要达到的目标,那么蜂巢头脑可能会更有用!
与上述相关,您无疑会发现Boost bind库和其他相关模块非常有用。
答案 2 :(得分:29)
我认为没有人在这里解释过一个问题是你需要“member pointers”而不是正常的函数指针。
函数的成员指针不仅仅是函数指针。在实现方面,编译器不能使用简单的函数地址,因为通常,在知道要取消引用哪个对象(想想虚函数)之前,您不知道要调用的地址。您还需要知道该对象,以便提供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方式找到函数指针。
编辑 - 这种方法称为“类方法”或“静态成员函数”。与非成员函数的主要区别在于,如果从类外部引用它,则必须使用::
范围解析运算符指定范围。例如,要获取函数指针,请使用&myclass::myfunction
并使用myclass::myfunction (arg);
调用它。
这种事情在使用旧的Win32 API时相当常见,这些API最初是为C而不是C ++设计的。当然,在这种情况下,参数通常是LPARAM或类似的而不是指针,并且需要一些转换。
答案 3 :(得分:18)
typedef void (Dog::*memfun)();
memfun doSomething = &Dog::bark;
....
(pDog->*doSomething)(); // if pDog is a pointer
// (pDog.*doSomething)(); // if pDog is a reference
答案 4 :(得分:9)
我来到这里是为了学习如何从方法创建函数指针(不是方法指针),但这里的答案都没有提供解决方案。所以我想到了它并找到了一个很好的解决方案,我认为值得分享:
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)
答案 5 :(得分:7)
最小可运行示例
#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);
}
您无法更改括号的顺序或省略它们。以下不起作用:
c.*p(2)
c.*(p)(2)
C ++ 11标准
.*
和->*
是用于此目的在C ++中引入的 singe运算符,并且不存在于C中。
.*
和->*
。答案 6 :(得分:6)
你不能使用函数指针调用成员函数的原因是 普通函数指针通常只是函数的内存地址。
要调用成员函数,您需要了解两件事:
普通函数指针不能同时存储。使用C ++成员函数指针 存储a),这就是在调用成员函数指针时需要显式指定实例的原因。
答案 7 :(得分:6)
指向类成员的函数指针是一个非常适合使用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);
}
答案 8 :(得分:2)
要创建新对象,您可以使用placement new,如上所述,或让您的类实现一个clone()方法来创建对象的副本。然后,您可以使用成员函数指针调用此clone方法,如上所述,以创建对象的新实例。克隆的优点是,有时您可能正在使用指向基类的指针,而您不知道对象的类型。在这种情况下,clone()方法可以更容易使用。此外,clone()将允许您复制对象的状态,如果这是您想要的。
答案 9 :(得分:1)
我用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来加快速度,也许使用数组数组而不是vector的unordered_map,但是我还没有弄清楚如何存储一个成员函数指针,并从对被调用类一无所知的代码中调用它。睫毛的答案看起来很有趣。