成员函数指针的实际用途是什么?

发布于 2024-09-28 02:15:19 字数 5657 浏览 2 评论 0原文

我已经通读了这篇文章,我从中得到的是当您想调用指向成员函数的指针时,您需要一个实例(指向一个的指针或堆栈引用)并这样调用它:

(instance.*mem_func_ptr)(..)
or
(instance->*mem_func_ptr)(..)

我的问题基于此:因为您实例,为什么不直接调用成员函数,如下所示:

instance.mem_func(..) //or: instance->mem_func(..)

指向成员函数的指针的合理/实际用途是什么?

[编辑]

我正在玩 X-development &达到了我正在实现小部件的阶段;用于将 X 事件转换为我的类的事件循环线程当事件到达时,小部件需要为每个小部件/窗口启动线程;为了正确地做到这一点,我认为我需要指向类中事件处理程序的函数指针。

事实并非如此:我确实发现我可以以更清晰和更清晰的方式做同样的事情。通过简单地使用虚拟基类来更简洁的方式。不需要任何指向成员函数的指针。正是在开发上述内容时,出现了对成员函数指针的实际可用性/含义的怀疑。

您需要对实例的引用才能使用成员函数指针,这一简单事实就不再需要引用了。

[编辑-@sbi &其他]

这是一个示例程序来说明我的观点: (特别注意“Handle_THREE()”)

#include <iostream>
#include <string>
#include <map>


//-----------------------------------------------------------------------------
class Base
{
public:
    ~Base() {}
    virtual void Handler(std::string sItem) = 0;
};

//-----------------------------------------------------------------------------
typedef void (Base::*memfunc)(std::string);

//-----------------------------------------------------------------------------
class Paper : public Base
{
public:
    Paper() {}
    ~Paper() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling paper\n"; }
};

//-----------------------------------------------------------------------------
class Wood : public Base
{
public:
    Wood() {}
    ~Wood() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling wood\n"; }
};


//-----------------------------------------------------------------------------
class Glass : public Base
{
public:
    Glass() {}
    ~Glass() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling glass\n"; }
};

//-----------------------------------------------------------------------------
std::map< std::string, memfunc > handlers;
void AddHandler(std::string sItem, memfunc f) { handlers[sItem] = f; }

//-----------------------------------------------------------------------------
std::map< Base*, memfunc > available_ONE;
void AddAvailable_ONE(Base *p, memfunc f) { available_ONE[p] = f; }

//-----------------------------------------------------------------------------
std::map< std::string, Base* > available_TWO;
void AddAvailable_TWO(std::string sItem, Base *p) { available_TWO[sItem] = p; }

//-----------------------------------------------------------------------------
void Handle_ONE(std::string sItem)
{
    memfunc f = handlers[sItem];
    if (f)
    {
        std::map< Base*, memfunc >::iterator it;
        Base *inst = NULL;
        for (it=available_ONE.begin(); ((it != available_ONE.end()) && (inst==NULL)); it++)
        {
            if (it->second == f) inst = it->first;
        }
        if (inst) (inst->*f)(sItem);
        else std::cout << "No instance of handler for: " << sItem << "\n";
    }
    else std::cout << "No handler for: " << sItem << "\n";
}

//-----------------------------------------------------------------------------
void Handle_TWO(std::string sItem)
{
    memfunc f = handlers[sItem];
    if (f)
    {
        Base *inst = available_TWO[sItem];
        if (inst) (inst->*f)(sItem);
        else std::cout << "No instance of handler for: " << sItem << "\n";
    }
    else std::cout << "No handler for: " << sItem << "\n";
}

//-----------------------------------------------------------------------------
void Handle_THREE(std::string sItem)
{
    Base *inst = available_TWO[sItem];
    if (inst) inst->Handler(sItem);
    else std::cout << "No handler for: " << sItem << "\n";
}


//-----------------------------------------------------------------------------
int main()
{
    Paper p;
    Wood w;
    Glass g;


    AddHandler("Paper", (memfunc)(&Paper::Handler));
    AddHandler("Wood", (memfunc)(&Wood::Handler));
    AddHandler("Glass", (memfunc)(&Glass::Handler));

    AddAvailable_ONE(&p, (memfunc)(&Paper::Handler));
    AddAvailable_ONE(&g, (memfunc)(&Glass::Handler));

    AddAvailable_TWO("Paper", &p);
    AddAvailable_TWO("Glass", &g);

    std::cout << "\nONE: (bug due to member-function address being relative to instance address)\n";
    Handle_ONE("Paper");
    Handle_ONE("Wood");
    Handle_ONE("Glass");
    Handle_ONE("Iron");

    std::cout << "\nTWO:\n";
    Handle_TWO("Paper");
    Handle_TWO("Wood");
    Handle_TWO("Glass");
    Handle_TWO("Iron");

    std::cout << "\nTHREE:\n";
    Handle_THREE("Paper");
    Handle_THREE("Wood");
    Handle_THREE("Glass");
    Handle_THREE("Iron");
}

{编辑] 上面示例中直接调用的潜在问题
在 Handler_THREE() 中,方法的名称必须是硬编码的,强制在使用它的任何地方进行更改,以将任何更改应用于该方法。使用指向成员函数的指针,唯一需要进行的额外更改是创建指针的位置。

[编辑]从答案中收集的实际用途

来自Chubsdad 的回答 :
内容:使用专用的“调用者”函数来调用 mem-func-ptr;
好处:使用其他对象提供的函数来保护代码
如何:如果特定函数是在很多地方使用并且名称和/或参数发生变化,那么您只需更改分配为指针的名称,并调整“Caller”函数中的调用。 (如果该函数用作 instance.function() ,则必须在各处进行更改。)

来自 Matthew Flaschen 的回答
内容:类中的局部特化
好处:使代码更清晰、更简单、更易于使用和维护
如何:用(可能)大型 switch()/if 替换传统上使用复杂逻辑实现的代码-then 带有直接指向专业化指针的语句;与上面的“Caller”功能非常相似。

I've read through this article, and what I take from it is that when you want to call a pointer to a member function, you need an instance (either a pointer to one or a stack-reference) and call it so:

(instance.*mem_func_ptr)(..)
or
(instance->*mem_func_ptr)(..)

My question is based on this: since you have the instance, why not call the member function directly, like so:

instance.mem_func(..) //or: instance->mem_func(..)

What is the rational/practical use of pointers to member functions?

[edit]

I'm playing with X-development & reached the stage where I am implementing widgets; the event-loop-thread for translating the X-events to my classes & widgets needs to start threads for each widget/window when an event for them arrives; to do this properly I thought I needed function-pointers to the event-handlers in my classes.

Not so: what I did discover was that I could do the same thing in a much clearer & neater way by simply using a virtual base class. No need whatsoever for pointers to member-functions. It was while developing the above that the doubt about the practical usability/meaning of pointers to member-functions arose.

The simple fact that you need a reference to an instance in order to use the member-function-pointer, obsoletes the need for one.

[edit - @sbi & others]

Here is a sample program to illustrate my point:
(Note specifically 'Handle_THREE()')

#include <iostream>
#include <string>
#include <map>


//-----------------------------------------------------------------------------
class Base
{
public:
    ~Base() {}
    virtual void Handler(std::string sItem) = 0;
};

//-----------------------------------------------------------------------------
typedef void (Base::*memfunc)(std::string);

//-----------------------------------------------------------------------------
class Paper : public Base
{
public:
    Paper() {}
    ~Paper() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling paper\n"; }
};

//-----------------------------------------------------------------------------
class Wood : public Base
{
public:
    Wood() {}
    ~Wood() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling wood\n"; }
};


//-----------------------------------------------------------------------------
class Glass : public Base
{
public:
    Glass() {}
    ~Glass() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling glass\n"; }
};

//-----------------------------------------------------------------------------
std::map< std::string, memfunc > handlers;
void AddHandler(std::string sItem, memfunc f) { handlers[sItem] = f; }

//-----------------------------------------------------------------------------
std::map< Base*, memfunc > available_ONE;
void AddAvailable_ONE(Base *p, memfunc f) { available_ONE[p] = f; }

//-----------------------------------------------------------------------------
std::map< std::string, Base* > available_TWO;
void AddAvailable_TWO(std::string sItem, Base *p) { available_TWO[sItem] = p; }

//-----------------------------------------------------------------------------
void Handle_ONE(std::string sItem)
{
    memfunc f = handlers[sItem];
    if (f)
    {
        std::map< Base*, memfunc >::iterator it;
        Base *inst = NULL;
        for (it=available_ONE.begin(); ((it != available_ONE.end()) && (inst==NULL)); it++)
        {
            if (it->second == f) inst = it->first;
        }
        if (inst) (inst->*f)(sItem);
        else std::cout << "No instance of handler for: " << sItem << "\n";
    }
    else std::cout << "No handler for: " << sItem << "\n";
}

//-----------------------------------------------------------------------------
void Handle_TWO(std::string sItem)
{
    memfunc f = handlers[sItem];
    if (f)
    {
        Base *inst = available_TWO[sItem];
        if (inst) (inst->*f)(sItem);
        else std::cout << "No instance of handler for: " << sItem << "\n";
    }
    else std::cout << "No handler for: " << sItem << "\n";
}

//-----------------------------------------------------------------------------
void Handle_THREE(std::string sItem)
{
    Base *inst = available_TWO[sItem];
    if (inst) inst->Handler(sItem);
    else std::cout << "No handler for: " << sItem << "\n";
}


//-----------------------------------------------------------------------------
int main()
{
    Paper p;
    Wood w;
    Glass g;


    AddHandler("Paper", (memfunc)(&Paper::Handler));
    AddHandler("Wood", (memfunc)(&Wood::Handler));
    AddHandler("Glass", (memfunc)(&Glass::Handler));

    AddAvailable_ONE(&p, (memfunc)(&Paper::Handler));
    AddAvailable_ONE(&g, (memfunc)(&Glass::Handler));

    AddAvailable_TWO("Paper", &p);
    AddAvailable_TWO("Glass", &g);

    std::cout << "\nONE: (bug due to member-function address being relative to instance address)\n";
    Handle_ONE("Paper");
    Handle_ONE("Wood");
    Handle_ONE("Glass");
    Handle_ONE("Iron");

    std::cout << "\nTWO:\n";
    Handle_TWO("Paper");
    Handle_TWO("Wood");
    Handle_TWO("Glass");
    Handle_TWO("Iron");

    std::cout << "\nTHREE:\n";
    Handle_THREE("Paper");
    Handle_THREE("Wood");
    Handle_THREE("Glass");
    Handle_THREE("Iron");
}

{edit] Potential problem with direct-call in above example:
In Handler_THREE() the name of the method must be hard-coded, forcing changes to be made anywhere that it is used, to apply any change to the method. Using a pointer to member-function the only additional change to be made is where the pointer is created.

[edit] Practical uses gleaned from the answers:

From answer by Chubsdad:
What: A dedicated 'Caller'-function is used to invoke the mem-func-ptr;
Benefit: To protect code using function(s) provided by other objects
How: If the particular function(s) are used in many places and the name and/or parameters change, then you only need to change the name where it is allocated as pointer, and adapt the call in the 'Caller'-function. (If the function is used as instance.function() then it must be changed everywhere.)

From answer by Matthew Flaschen:
What: Local specialization in a class
Benefit: Makes the code much clearer,simpler and easier to use and maintain
How: Replaces code that would conventionally be implement using complex logic with (potentially) large switch()/if-then statements with direct pointers to the specialization; fairly similar to the 'Caller'-function above.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

云柯 2024-10-05 02:15:19

与使用任何函数指针的原因相同:您可以在调用之前使用任意程序逻辑来设置函数指针变量。您可以使用开关、if/else,将其传递到函数中,等等。

编辑:

问题中的示例确实表明您有时可以使用虚函数作为指向成员函数的指针的替代方法。这并不奇怪,因为编程通常有多种方法。

这是一个虚拟函数可能没有意义的例子。就像OP中的代码一样,这只是为了说明,而不是特别现实。它显示了一个具有公共测试功能的类。它们使用内部的、私有的函数。内部函数只能在setup之后调用,并且必须在之后调用teardown。

#include <iostream>

class MemberDemo;
typedef void (MemberDemo::*MemberDemoPtr)();

class MemberDemo
{
    public:
    void test1();
    void test2();

    private:
    void test1_internal();
    void test2_internal();
    void do_with_setup_teardown(MemberDemoPtr p);
};

void MemberDemo::test1()
{
    do_with_setup_teardown(&MemberDemo::test1_internal);
}

void MemberDemo::test2()
{
    do_with_setup_teardown(&MemberDemo::test2_internal);
}

void MemberDemo::test1_internal()
{
    std::cout << "Test1" << std::endl;
}

void MemberDemo::test2_internal()
{
    std::cout << "Test2" << std::endl;
}

void MemberDemo::do_with_setup_teardown(MemberDemoPtr mem_ptr)
{
    std::cout << "Setup" << std::endl;
    (this->*mem_ptr)();
    std::cout << "Teardown" << std::endl;
}

int main()
{
    MemberDemo m;
    m.test1();
    m.test2();
}

The same reason you use any function pointer: You can use arbitrary program logic to set the function pointer variable before calling it. You could use a switch, an if/else, pass it into a function, whatever.

EDIT:

The example in the question does show that you can sometimes use virtual functions as an alternative to pointers to member functions. This shouldn't be surprising, because there are usually multiple approaches in programming.

Here's an example of a case where virtual functions probably don't make sense. Like the code in the OP, this is meant to illustrate, not to be particularly realistic. It shows a class with public test functions. These use internal, private, functions. The internal functions can only be called after a setup, and a teardown must be called afterwards.

#include <iostream>

class MemberDemo;
typedef void (MemberDemo::*MemberDemoPtr)();

class MemberDemo
{
    public:
    void test1();
    void test2();

    private:
    void test1_internal();
    void test2_internal();
    void do_with_setup_teardown(MemberDemoPtr p);
};

void MemberDemo::test1()
{
    do_with_setup_teardown(&MemberDemo::test1_internal);
}

void MemberDemo::test2()
{
    do_with_setup_teardown(&MemberDemo::test2_internal);
}

void MemberDemo::test1_internal()
{
    std::cout << "Test1" << std::endl;
}

void MemberDemo::test2_internal()
{
    std::cout << "Test2" << std::endl;
}

void MemberDemo::do_with_setup_teardown(MemberDemoPtr mem_ptr)
{
    std::cout << "Setup" << std::endl;
    (this->*mem_ptr)();
    std::cout << "Teardown" << std::endl;
}

int main()
{
    MemberDemo m;
    m.test1();
    m.test2();
}
梦在深巷 2024-10-05 02:15:19

我的问题是基于此:既然有了实例,为什么不直接调用成员函数[?]

前面:在超过 15 年的 C++ 编程中,我可能使用过成员指针两次或三次。有了虚拟函数,它就没有多大用处了。

如果您想调用一个对象(或多个对象)上的某些成员函数,并且您必须先决定调用哪个成员函数,然后才能找到要调用的对象,则可以使用它们。 这里是有人想要这样做的一个例子。

My question is based on this: since you have the instance, why not call the member function directly[?]

Upfront: In more than 15 years of C++ programming, I have used members pointers maybe twice or thrice. With virtual functions being around, there's not all that much use for it.

You would use them if you want to call a certain member functions on an object (or many objects) and you have to decide which member function to call before you can find out for which object(s) to call it on. Here is an example of someone wanting to do this.

冬天的雪花 2024-10-05 02:15:19

我发现,当您查看诸如 boost::bind() 等更高级别的构造时,成员函数指针的真正用处就体现出来了。这将允许您将函数调用包装为一个对象,该对象可以稍后绑定到特定的对象实例,然后作为可复制对象传递。这是一个非常强大的习惯用法,允许延迟回调、委托和复杂的谓词操作。请参阅我之前的文章了解一些示例:

https://stackoverflow。 com/questions/1596139/hidden-features-and-dark-corners-of-stl/1596626#1596626

I find the real usefulness of pointers to member functions comes when you look at a higher level construct such as boost::bind(). This will let you wrap a function call as an object that can be bound to a specific object instance later on and then passed around as a copyable object. This is a really powerful idiom that allows for deferred callbacks, delegates and sophisticated predicate operations. See my previous post for some examples:

https://stackoverflow.com/questions/1596139/hidden-features-and-dark-corners-of-stl/1596626#1596626

尐籹人 2024-10-05 02:15:19

成员函数与许多函数指针一样,充当回调。您可以通过创建一些调用您的方法的抽象类来管理没有它们的情况,但这可能需要大量额外的工作。

一种常见的用途是算法。在 std::for_each 中,我们可能想要调用集合中每个成员的类的成员函数。我们还可能想对集合的每个成员调用我们自己类的成员函数 - 后者需要 boost::bind 来实现,前者可以使用 STL mem_fun 系列类来完成(如果我们没有shared_ptr 的集合,在这种情况下我们也需要 boost::bind )。我们还可以在某些查找或排序算法中使用成员函数作为谓词。 (这消除了我们编写一个自定义类来重载operator()来调用我们类的成员的需要,我们只需将它直接传递给boost::bind)。

正如我提到的,另一个用途是回调,通常在事件驱动的代码中。当操作完成时,我们希望调用类的方法来处理完成。这通常可以包装到 boost::bind 函子中。在这种情况下,我们必须非常小心地正确管理这些对象的生命周期及其线程安全性(特别是如果出现问题,调试起来会非常困难)。尽管如此,它再次可以让我们免于编写大量的“包装”代码。

Member functions, like many function pointers, act as callbacks. You could manage without them by creating some abstract class that calls your method, but this can be a lot of extra work.

One common use is algorithms. In std::for_each, we may want to call a member function of the class of each member of our collection. We also may want to call the member function of our own class on each member of the collection - the latter requires boost::bind to achieve, the former can be done with the STL mem_fun family of classes (if we don't have a collection of shared_ptr, in which case we need to boost::bind in this case too). We could also use a member function as a predicate in certain lookup or sort algorithms. (This removes our need to write a custom class that overloads operator() to call a member of our class, we just pass it in directly to boost::bind).

The other use, as I mentioned, are callbacks, often in event-driven code. When an operation has completed we want a method of our class called to handle the completion. This can often be wrapped into a boost::bind functor. In this case we have to be very careful to manage the lifetime of these objects correctly and their thread-safety (especially as it can be very hard to debug if something goes wrong). Still, it once again can save us from writing large amounts of "wrapper" code.

寄意 2024-10-05 02:15:19

有很多实际用途。我想到的一个如下:

假设有一个如下所示的核心函数(适当定义了 myfoo 和 MFN),

void dosomething(myfoo &m, MFN f){   // m could also be passed by reference to 
                                     // const
   m.*f();
}

这样的函数在存在指向成员函数的指针时,对扩展开放,对修改关闭(OCP

另请参阅 安全布尔习惯用法,巧妙地使用指向成员的指针。

There are many practical uses. One that comes to my mind is as follows:

Assume a core function such as below (suitably defined myfoo and MFN)

void dosomething(myfoo &m, MFN f){   // m could also be passed by reference to 
                                     // const
   m.*f();
}

Such a function in the presence of pointer to member functions, becomes open for extension and closed for modification (OCP)

Also refer to Safe bool idiom which smartly uses pointer to members.

萌︼了一个春 2024-10-05 02:15:19

成员函数指针的最佳用途是打破依赖关系。

需要指向成员函数的指针的一个很好的例子是订阅者/发布者模式:

http://en.wikipedia。 org/wiki/发布/订阅

The best use of pointers to member functions is to break dependencies.

Good example where pointer to member function is needed is Subscriber/Publisher pattern :

http://en.wikipedia.org/wiki/Publish/subscribe

爱的那么颓废 2024-10-05 02:15:19

在我看来,成员函数指针的原始形式对于普通程序员来说并不是很有用。 OTOH,像 ::std::tr1::function 这样的构造将成员函数指针与指向它们应该操作的对象的指针包装在一起,非常有用。

当然 ::std::tr1::function 非常复杂。因此,我将给您一个简单的示例,如果您有 ::std::tr1::function 可用,您实际上不会在实践中使用该示例:

// Button.hpp
#include <memory>

class Button {
 public:
   Button(/* stuff */) : hdlr_(0), myhandler_(false) { }
   ~Button() {
      // stuff
      if (myhandler_) {
         delete hdlr_;
      }
   }
   class PressedHandler {
    public:
      virtual ~PressedHandler() = 0;

      virtual void buttonPushed(Button *button) = 0;
   };

   // ... lots of stuff

   // This stores a pointer to the handler, but will not manage the
   // storage.  You are responsible for making sure the handler stays
   // around as long as the Button object.
   void setHandler(const PressedHandler &hdlr) {
      hdlr_ = &hdlr;
      myhandler_ = false;
   }

   // This stores a pointer to an object that Button does not manage.  You
   // are responsible for making sure this object stays around until Button
   // goes away.
   template <class T>
   inline void setHandlerFunc(T &dest, void (T::*pushed)(Button *));

 private:
   const PressedHandler *hdlr_;
   bool myhandler_;

   template <class T>
   class PressedHandlerT : public Button::PressedHandler {
    public:
      typedef void (T::*hdlrfuncptr_t)(Button *);

      PressedHandlerT(T *ob, hdlrfuncptr_t hdlr) : ob_(ob), func_(hdlr) { }
      virtual ~PressedHandlerT() {}

      virtual void buttonPushed(Button *button) { (ob_->*func_)(button); }

    private:
      T * const ob_;
      const hdlrfuncptr_t func_;
   };
};

template <class T>
inline void Button::setHandlerFunc(T &dest, void (T::*pushed)(Button *))
{
   PressedHandler *newhandler = new PressedHandlerT<T>(&dest, pushed);
   if (myhandler_) {
      delete hdlr_;
   }
   hdlr_ = newhandler;
   myhandler_ = true;
}

// UseButton.cpp
#include "Button.hpp"
#include <memory>

class NoiseMaker {
 public:
   NoiseMaker();
   void squee(Button *b);
   void hiss(Button *b);
   void boo(Button *b);

 private:
   typedef ::std::auto_ptr<Button> buttonptr_t;
   const buttonptr_t squeebutton_, hissbutton_, boobutton_;
};


NoiseMaker::NoiseMaker()
     : squeebutton_(new Button), hissbutton_(new Button), boobutton_(new Button)
{
   squeebutton_->setHandlerFunc(*this, &NoiseMaker::squee);
   hissbutton_->setHandlerFunc(*this, &NoiseMaker::hiss);
   boobutton_->setHandlerFunc(*this, &NoiseMaker::boo);
}

假设 Button 位于库并且您无法更改,我很高兴看到您使用虚拟基类干净地实现该库,而无需在某处诉诸 switchif else if 构造。

In my opinion, member function pointers do are not terribly useful to the average programmer in their raw form. OTOH, constructs like ::std::tr1::function that wrap member function pointers together with a pointer to the object they're supposed to operate on are extremely useful.

Of course ::std::tr1::function is very complex. So I will give you a simple example that you wouldn't actually use in practice if you had ::std::tr1::function available:

// Button.hpp
#include <memory>

class Button {
 public:
   Button(/* stuff */) : hdlr_(0), myhandler_(false) { }
   ~Button() {
      // stuff
      if (myhandler_) {
         delete hdlr_;
      }
   }
   class PressedHandler {
    public:
      virtual ~PressedHandler() = 0;

      virtual void buttonPushed(Button *button) = 0;
   };

   // ... lots of stuff

   // This stores a pointer to the handler, but will not manage the
   // storage.  You are responsible for making sure the handler stays
   // around as long as the Button object.
   void setHandler(const PressedHandler &hdlr) {
      hdlr_ = &hdlr;
      myhandler_ = false;
   }

   // This stores a pointer to an object that Button does not manage.  You
   // are responsible for making sure this object stays around until Button
   // goes away.
   template <class T>
   inline void setHandlerFunc(T &dest, void (T::*pushed)(Button *));

 private:
   const PressedHandler *hdlr_;
   bool myhandler_;

   template <class T>
   class PressedHandlerT : public Button::PressedHandler {
    public:
      typedef void (T::*hdlrfuncptr_t)(Button *);

      PressedHandlerT(T *ob, hdlrfuncptr_t hdlr) : ob_(ob), func_(hdlr) { }
      virtual ~PressedHandlerT() {}

      virtual void buttonPushed(Button *button) { (ob_->*func_)(button); }

    private:
      T * const ob_;
      const hdlrfuncptr_t func_;
   };
};

template <class T>
inline void Button::setHandlerFunc(T &dest, void (T::*pushed)(Button *))
{
   PressedHandler *newhandler = new PressedHandlerT<T>(&dest, pushed);
   if (myhandler_) {
      delete hdlr_;
   }
   hdlr_ = newhandler;
   myhandler_ = true;
}

// UseButton.cpp
#include "Button.hpp"
#include <memory>

class NoiseMaker {
 public:
   NoiseMaker();
   void squee(Button *b);
   void hiss(Button *b);
   void boo(Button *b);

 private:
   typedef ::std::auto_ptr<Button> buttonptr_t;
   const buttonptr_t squeebutton_, hissbutton_, boobutton_;
};


NoiseMaker::NoiseMaker()
     : squeebutton_(new Button), hissbutton_(new Button), boobutton_(new Button)
{
   squeebutton_->setHandlerFunc(*this, &NoiseMaker::squee);
   hissbutton_->setHandlerFunc(*this, &NoiseMaker::hiss);
   boobutton_->setHandlerFunc(*this, &NoiseMaker::boo);
}

Assuming Button is in a library and not alterable by you, I would enjoy seeing you implement that cleanly using a virtual base class without resorting to a switch or if else if construct somewhere.

╰◇生如夏花灿烂 2024-10-05 02:15:19

指向成员函数类型的指针的全部要点是它们充当引用特定方法的运行时方式。当您使用“常用”语法进行方法访问时,

object.method();
pointer->method();

method 部分是您要调用的方法的固定编译时规范。它被硬编码到您的程序中。它永远不会改变。但是,通过使用指向成员函数类型的指针,您可以用在方法的运行时规范中可更改的变量替换该固定部分。

为了更好地说明这一点,让我做以下简单的类比。假设您有一个数组,

int a[100];

您可以使用固定的编译时索引访问其元素,

a[5]; a[8]; a[23];

在这种情况下,特定索引被硬编码到您的程序中。但是您也可以使用运行时索引访问数组的元素 - 一个整数变量i

a[i];

i的值不是固定的,它可以在运行时改变,从而允许您可以在运行时选择数组的不同元素。这与指向成员函数类型的指针的功能非常相似。

您提出的问题(“既然您有实例,为什么不直接调用成员函数”)可以转换为这个数组上下文。您基本上是在问:“当我们有像 a[1]这样的直接编译时常量访问时,为什么我们需要变量索引访问 a[i] >a[3]?”我希望您知道这个问题的答案并认识到特定数组元素的运行时选择的价值。

这同样适用于指向成员函数类型的指针:它们再次允许您执行特定类方法的运行时选择。

The whole point of pointers of pointer-to-member function type is that they act as a run-time way to reference a specific method. When you use the "usual" syntax for method access

object.method();
pointer->method();

the method part is a fixed, compile-time specification of the method you want to call. It is hardcoded into your program. It can never change. But by using a pointer of pointer-to-member function type you can replace that fixed part with a variable, changeable at run-time specification of the method.

To better illustrate this, let me make the following simple analogy. Let's say you have an array

int a[100];

You can access its elements with fixed compile-time index

a[5]; a[8]; a[23];

In this case the specific indices are hardcoded into your program. But you can also access array's elements with a run-time index - an integer variable i

a[i];

the value of i is not fixed, it can change at run-time, thus allowing you to select different elements of the array at run-time. That is very similar to what pointers of pointer-to-member function type let you do.

The question you are asking ("since you have the instance, why not call the member function directly") can be translated into this array context. You are basically asking: "Why do we need a variable index access a[i], when we have direct compile-time constant access like a[1] and a[3]?" I hope you know the answer to this question and realize the value of run-time selection of specific array element.

The same applies to pointers of pointer-to-member function type: they, again, let you to perform run-time selection of a specific class method.

瘫痪情歌 2024-10-05 02:15:19

用例是您有多个具有相同签名的成员方法,并且您想要构建在给定情况下应该调用哪个成员方法的逻辑。这有助于实现状态机算法。

不是日常用的东西...

The use case is that you have several member methods with the same signature, and you want to build logic which one should be called under given circumstances. This can be helpful to implement state machine algorithms.

Not something you use everyday...

只怪假的太真实 2024-10-05 02:15:19

想象一下,您有一个函数可以根据传递的参数调用多个不同函数之一。

您可以使用巨大的 if/else if 语句
您可以使用 switch 语句
或者您可以使用函数指针表(跳转表)

如果您有很多不同的选项,则跳转表可以是一种更简洁的方式来安排代码......

但这取决于个人喜好。无论如何,Switch 语句和跳转表对应于或多或少相同的编译代码:)

Imagine for a second you have a function that could call one of several different functions depending on parameters passed.

You could use a giant if/else if statement
You could use a switch statement
Or you could use a table of function pointers (a jump table)

If you have a lot of different options the jump table can be a much cleaner way of arranging your code ...

Its down to personal preference though. Switch statement and jump table correspond to more or less the same compiled code anyway :)

云雾 2024-10-05 02:15:19

成员指针+模板=纯粹的胜利。

例如 如何判断类在编译时是否包含某个成员函数

template<typename TContainer,
         typename TProperty,
         typename TElement = decltype(*Container().begin())>
TProperty grand_total(TContainer& items, TProperty (TElement::*property)() const)
{
   TProperty accum = 0;
   for( auto it = items.begin(), end = items.end(); it != end; ++it) {
       accum += (it->*property)();
   }
   return accum;
}

auto ship_count = grand_total(invoice->lineItems, &LineItem::get_quantity);
auto sub_total = grand_total(invoice->lineItems, &LineItem::get_extended_total);
auto sales_tax = grand_total(invoice->lineItems, &LineItem::calculate_tax);

Member pointers + templates = pure win.

e.g. How to tell if class contains a certain member function in compile time

or

template<typename TContainer,
         typename TProperty,
         typename TElement = decltype(*Container().begin())>
TProperty grand_total(TContainer& items, TProperty (TElement::*property)() const)
{
   TProperty accum = 0;
   for( auto it = items.begin(), end = items.end(); it != end; ++it) {
       accum += (it->*property)();
   }
   return accum;
}

auto ship_count = grand_total(invoice->lineItems, &LineItem::get_quantity);
auto sub_total = grand_total(invoice->lineItems, &LineItem::get_extended_total);
auto sales_tax = grand_total(invoice->lineItems, &LineItem::calculate_tax);
放低过去 2024-10-05 02:15:19

要调用它,您需要一个对实例的引用,但随后您可以调用 func direct &不需要指向它的指针。

这完全没有抓住重点。这里有两个独立的问题:

  • 在稍后的某个时间点采取什么操作,
  • 对什么对象执行该操作

拥有对实例的引用满足第二个要求。指向成员函数的指针解决了第一个问题:它们是一种非常直接的方式来记录(在程序执行的某个时刻)应在执行的稍后阶段(可能由程序的另一部分)采取哪些操作。

示例

假设您有一只猴子,它可以亲吻人类或挠痒痒。下午 6 点,您的程序应该将猴子放开,并知道猴子应该访问谁,但下午 3 点左右,您的用户将输入应采取的操作。

初学者的方法

因此,下午 3 点你可以设置一个变量“enum Action { Kiss, Tickle } action;”,然后下午 6 点你可以执行类似“if (action == Kiss) Monkey->kiss(person); else”的操作猴子->挠痒痒(人)”。

问题

但是引入了额外的编码级别(引入了 Action 类型来支持这一点 - 可以使用内置类型,但更容易出错并且本质上意义较小)。然后 - 在确定下午 3 点应该采取什么操作后,下午 6 点,您必须冗余地查阅该编码值来决定采取哪个操作,这将需要另一个 if/else 或切换编码值。这一切都很笨拙、冗长、缓慢且容易出错。

成员函数指针

更好的方法是使用更专门的变量 - 成员函数指针 - 它直接记录下午 6 点要执行的操作。这就是成员函数指针。这是一个之前设置的亲吻或挠痒选择器,为猴子创建一个“状态” - 是挠痒痒的还是亲吻的 - 可以稍后使用。后面的代码只是调用已设置的任何函数,而无需考虑可能性或具有任何 if/else-if 或 switch 语句。

要调用它,您需要一个对实例的引用,但随后您可以调用 func direct &不需要指向它的指针。

回到这一点。因此,如果您在编译时决定要采取哪些操作(即程序中的 X 点,这肯定会很棘手),那么这很好。当您不确定并且想要将操作的设置与这些操作的调用分离时,可以使用函数指针。

To invoke it, you need a reference to an instance, but then you can call the func direct & don't need a pointer to it.

This is completely missing the point. There are two indepedent concerns here:

  • what action to take at some later point in time
  • what object to perform that action on

Having a reference to an instance satisfies the second requirement. Pointers to member functions address the first: they are a very direct way to record - at one point in a program's execution - which action should be taken at some later stage of execution, possibly by another part of the program.

EXAMPLE

Say you have a monkey that can kiss people or tickle them. At 6pm, your program should set the monkey loose, and knows whom the monkey should visit, but around 3pm your user will type in which action should be taken.

A beginner's approach

So, at 3pm you could set a variable "enum Action { Kiss, Tickle } action;", then at 6pm you could do something like "if (action == Kiss) monkey->kiss(person); else monkey->tickle(person)".

Issues

But that introducing an extra level of encoding (the Action type's introduced to support this - built in types could be used but would be more error prone and less inherently meaningful). Then - after having worked out what action should be taken at 3pm, at 6pm you have to redundantly consult that encoded value to decide which action to take, which will require another if/else or switch upon the encoded value. It's all clumsy, verbose, slow and error prone.

Member function pointers

A better way is to use a more specialised varibale - a member function pointer - that directly records which action to perform at 6pm. That's what a member function pointer is. It's a kiss-or-tickle selector that's set earlier, creating a "state" for the monkey - is it a tickler or a kisser - which can be used later. The later code just invokes whatever function's been set without having to think about the possibilities or have any if/else-if or switch statements.

To invoke it, you need a reference to an instance, but then you can call the func direct & don't need a pointer to it.

Back to this. So, this is good if you make the decision about which action to take at compile time (i.e. a point X in your program, it'll definitely be a tickle). Function pointers are for when you're not sure, and want to decouple the setting of actions from the invocation of those actions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文