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

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

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


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

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


这可能吗?如果可以的话,这样做的首选方式是什么?

评论

如果您要调用对象成员函数然后仅将指针传递给该对象,我仍然不太明白“为什么”?如果人们抱怨,因为它使您可以更好地封装类,为什么不创建所有类都继承自的接口类呢?

尽管很多人会使用boost :: function来隐藏原始成员指针机制,但是它在实现类似命令模式的过程中可能很有用。

您为什么动态分配那只狗?然后,您也必须手动删除该对象。这看起来很像您来自Java,C#或其他一些类似的语言,并且仍在使用C ++。一个普通的自动对象(狗狗)更可能是您想要的。

@乍得:我大多数都会同意,但是有时候通过引用会更昂贵。考虑一个循环遍历某种类型的数据(解析,计算等)的循环,而不是能够基于某些if / else计算来调用一个函数,这会带来成本,而仅调用指向的函数也可以避免这种if / then / else检查是否可以在进入循环之前完成这些检查。

另请参见指向成员函数的函数指针。

#1 楼

详细阅读:

// 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 Wilke
2014-2-17的3:51

为什么必须将pt2ConstMember初始化为NULL?

– Ciro Santilli郝海东冠状病六四事件法轮功
2015年7月1日9:00

@AlexisWilke为什么会令人惊讶?对于直接对象(不是指针),它是(object。* method_pointer),因此我们希望*具有更高的优先级。

– Ciro Santilli郝海东冠状病六四事件法轮功
15年7月1日在9:02

@TomášZato,如果我没记错的话(也许是),这只是用来说明你将。*应用于的内容应该是(子)类实例的指针。但这对我来说是新语法,我只是根据其他答案和此处链接的资源进行猜测。我建议进行修改以使其更清楚。

–c1moore
18年1月31日23:15

哦,快点,恭喜100!

–乔纳森·梅(Jonathan Mee)
18/12/17在20:53

#2 楼


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


最简单的方法是从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*,但是函数指针的类型确实可以不遵循类层次结构的查找规则。因此,动物方法指针与狗方法指针不兼容,换句话说,您不能将Dog* (*)()分配给类型为Animal* (*)()的变量。

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

已经回答了上述问题,我想知道是否有更好的方法来实现所需的功能。在某些特定情况下,您会做这种事情,但是您可能会发现还有其他模式可以更好地解决您的问题。如果以更笼统的方式描述要达到的目标,那么蜂巢式思维可能会变得更加有用!

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

评论


我已经使用C ++十多年了,并定期学习新知识。我以前从未听说过-> *,但现在希望我永远不需要它了:)

–托马斯
09年9月28日在11:37

#3 楼

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

函数的成员指针不仅仅是函数指针。用实现的术语来说,编译器不能使用简单的函数地址,因为通常来说,您不知道要调用的地址,直到知道要取消引用的对象(考虑虚函数)为止。当然,您还需要知道该对象以便提供this隐式参数。

您已经说过需要它们,现在我要说真的需要避免它们。严重的是,成员指针很痛苦。看看达到相同目标的面向对象设计模式,或者使用boost::function或上面提到的任何方法,要理智得多,那就是明智的选择。

如果您正在提供指向现有代码的函数指针,因此您确实需要一个简单的函数指针,因此应将函数编写为类的静态成员。静态成员函数不了解this,因此您需要将对象作为显式参数传递。对于使用需要函数指针的旧C代码,这方面曾经有一个非同寻常的习惯用法

除了问题),可以使用常规C方式找到函数指针。

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

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

评论


如果正常情况下您是指C风格的函数,则“ myfunction”不是标准函数。 “ myfunction”更准确地称为myclass方法。类的方法与普通函数不同,因为它们具有C样式函数所不具有的东西(即“ this”指针)。

–埃里克
2012年10月25日17:57

建议使用boost是严厉的。使用方法指针有很多实际的理由。我不介意使用boost作为替代方法,但是当有人说别人应该在不了解所有事实的情况下使用它时,我会讨厌。加速需要付出代价!而且,如果这是一个嵌入式平台,那么这可能不是一个选择。除此之外,我真的很喜欢你的文章。

–埃里克
2012年10月25日在18:10

@Eric-关于您的第二点,我无意说“您应该使用Boost”,实际上,我从未使用过Boost。目的(据我三年后所知)是人们应该寻找替代品,并列举一些可能性。 “或其他内容”表示列表并非详尽无遗。成员指针会增加可读性。它们的简洁的源表示形式也可以掩盖运行时成本-特别是指向方法的成员指针必须同时处理非虚拟方法和虚拟方法,并且必须知道哪个方法。

–Steve314
2012年10月25日在22:09

@Eric-不仅如此,这些问题也是成员指针不可移植的原因-Visual C ++,至少在过去,需要一些有关如何表示成员指针类型的线索。对于嵌入式系统,我将使用静态函数方法-指针的表示形式与任何其他函数指针相同,其成本显而易见,并且不存在可移植性问题。并且由静态成员函数包装的调用知道(在编译时)该调用是否是虚拟的-除了对虚拟方法的常规vtable查找之外,不需要运行时检查。

–Steve314
2012年10月25日在22:15



@Eric-在您的第一点上,我知道静态成员函数与C样式函数并不完全相同(因此,“作用域除外”),但我可能应该包括该名称。

–Steve314
2012年10月25日在22:23

#4 楼

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


评论


应该是:(pDog-> * doSomething)(); //如果pDog是指针// //(pDog。* doSomething)(); //如果pDog是引用,因为()运算符具有更高的优先级,则-> *和。*。

–柚木
09年9月28日在9:13

#5 楼

最小的可运行示例

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 standard

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

C ++ 11 N3337标准草案:


2.13“运算符和标点符号”列出了所有运算符,其中包含.*->*
5.5“指针对成员运算符”说明了它们的作用


#6 楼

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

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>


因此,以您的示例为例,您现在可以这样做: >编辑:
使用C ++ 17,还有一个更好的解决方案:

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


可以直接使用而无需宏:

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


对于带有const之类的修饰符的方法,您可能需要更多特殊化,例如:

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


#7 楼

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

要调用成员函数,您需要了解两点:


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

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

评论


我对此投了赞成票,但要在OP不知道“哪个实例”指的是什么的情况下,请增加一个澄清点。我将扩展解释固有的“ this”指针。

–埃里克
2012年10月25日18:01

#8 楼

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


#9 楼

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

评论


克隆可能会很昂贵,并且如果性能是一个问题或引起一些关注,OP可能希望避免使用它们。

–埃里克
2012年10月25日17:58

#10 楼

我用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,但是我还没有弄清楚如何存储成员函数指针,并从对被调用类一无所知的代码中调用它。睫毛的答案很有趣..