class Dog : Animal
{
Dog ();
void bark ();
}
…
Dog* pDog = new Dog ();
BarkFunction pBark = &Dog::bark;
(*pBark) (pDog);
…
如果可能的话,我也想通过指针调用构造函数。
NewAnimalFunction pNew = &Dog::Dog;
Animal* pAnimal = (*pNew)();
这个可能吗?如果可以的话,最好的方法是什么?
class Dog : Animal
{
Dog ();
void bark ();
}
…
Dog* pDog = new Dog ();
BarkFunction pBark = &Dog::bark;
(*pBark) (pDog);
…
NewAnimalFunction pNew = &Dog::Dog;
Animal* pAnimal = (*pNew)();
阅读此链接了解详情:
// 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');
*this.*pt2Member
可能会让人感到惊讶。 *
的优先级高于.*
...就我个人而言,我仍然会写成this->*pt2Member
,这样可以少用一个运算符。 - Alexis Wilkept2ConstMember
初始化为 NULL
? - Ciro Santilli OurBigBook.com(object.*method_pointer)
,因此我们希望 *
具有更高的优先级。 - Ciro Santilli OurBigBook.comthis
只是用来演示你应该将.*
应用于指向(子)类实例的指针。然而,这对我来说是新语法,我只是根据其他答案和链接资源猜测。我建议进行编辑以使其更清晰明了。 - c1moore*this
来调用函数?那真的有必要吗? this->*pt2ConstMember)(12, 'a', 'b')
呢?那样可行吗?this->pt2ConstMember)(12, 'a', 'b')
呢?那样可行吗?请参见我的问题末尾的答案摘要。我相信在将其作为函数调用时,在指向 const 成员函数的指针 pt2ConstMember
前面的 *
是可选的。 - Gabriel Staples如何获取类成员函数的函数指针,并在稍后使用特定对象调用该成员函数?
最简单的方法是使用typedef
。对于成员函数,您需要在类型声明中添加类名:
typedef void(Dog::*BarkFunction)(void);
然后要调用该方法,您可以使用->*
运算符:
(pDog->*pBark)();
另外,如果可能的话,我也想通过指针调用构造函数。这是可能的吗?如果可以,那么最好的方法是什么?
我认为你无法像这样使用构造函数 - 构造函数和析构函数是特殊的。达到这种目的的正常方法是使用工厂模式,它基本上只是调用构造函数的静态函数。下面的代码提供了一个示例。
我修改了你的代码,以实现你基本上描述的内容。以下有一些注意事项。
#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 库和其他相关模块非常有用。
->*
,但现在希望永远不需要用到它 :) - Thomas我认为没有人在这里解释过一个问题,那就是你需要 "成员指针" 而不是普通函数指针。
函数的成员指针并不仅仅是函数指针。在实现方面,编译器不能使用简单的函数地址,因为一般来说,在你不知道要引用哪个对象时你不知道要调用哪个地址(想想虚函数)。当然,你还需要知道对象以提供“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或类似的指针,需要进行一些转换。
最小化可运行的示例
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)
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语言中不存在。
.*
和 ->*
。typedef void (Dog::*memfun)();
memfun doSomething = &Dog::bark;
....
(pDog->*doSomething)(); // if pDog is a pointer
// (pDog.*doSomething)(); // if pDog is a reference
我来这里学习如何从方法中创建一个函数指针(而不是方法指针),但这里的答案都没有提供解决方案。以下是我想出的解决方法:
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...);
}
};
类成员的函数指针问题很适合使用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);
}
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);
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以及使用数组而不是向量的无序映射来加速它,但我还没有完全弄清楚如何存储成员函数指针并从不知道被调用的类的代码中调用它。Eyelash的答案看起来非常有趣。
Dog dog;
)更符合你的需求。 - sbi