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

时间:2010-10-18 07:50:49

标签: c++ member-functions

我已阅读this article,我从中获取的是当你想调用指向成员函数的指针时,你需要一个实例(指向一个实例的指针或一个堆栈引用)并称之为:

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

我的问题是基于这样的:因为你拥有实例,为什么不直接调用成员函数,如下所示:

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

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

[编辑]

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

不是这样的:我发现的是,我可以更清楚地做同样的事情。简单地使用虚拟基类的简洁方法。无需任何指向成员函数的指针。正是在开发上述内容时,出现了对成员函数指针的实际可用性/含义的疑问。

为了使用member-function-pointer,你需要引用一个实例的简单事实,废除了对一个实例的需要。

[编辑 - @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");
}

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

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

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

来自answer by Matthew Flaschen
内容:类中的本地特化
优点:使代码更清晰,更简单,更易于使用和维护
如何:替换通常使用复杂逻辑实现的代码(可能)大的switch() / if-then语句直接指向专门化;与上面的“来电者”功能非常相似。

13 个答案:

答案 0 :(得分:11)

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

编辑:

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

以下是虚拟功能可能没有意义的情况示例。与OP中的代码一样,这是为了说明,而不是特别现实。它显示了一个具有公共测试功能的类。这些功能使用内部私有功能。内部函数只能在设置后调用,之后必须调用拆解。

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

答案 1 :(得分:8)

  

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

Upfront:在超过15年的C ++编程中,我使用成员指针可能是两次或三次。随着虚拟功能的出现,它并没有那么多用处。

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

答案 2 :(得分:4)

我发现在查看更高级别的构造(如boost::bind())时,会发现成员函数指针的实际用处。这将允许您将函数调用包装为一个对象,该对象稍后可以绑定到特定对象实例,然后作为可复制对象传递。这是一个非常强大的习惯用法,允许延迟回调,委托和复杂的谓词操作。有关示例,请参阅我之前的帖子:

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

答案 3 :(得分:4)

与许多函数指针一样,成员函数充当回调函数。你可以通过创建一个调用你的方法的抽象类来管理它们,但这可能是一项额外的工作。

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

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

答案 4 :(得分:3)

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

假设核心功能如下(适当定义的myfoo和MFN)

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

在存在指向成员函数的指针的情况下,这样的函数变为开放以进行扩展并关闭以进行修改(OCP

另请参阅Safe bool idiom,它巧妙地使用指向成员的指针。

答案 5 :(得分:3)

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

需要指向成员函数的指针的好例子是Subscriber / Publisher模式:

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

答案 6 :(得分:3)

在我看来,成员函数指针对于原始形式的普通程序员来说并不是非常有用。 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构造。

答案 7 :(得分:3)

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

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

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

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

int a[100];

您可以使用固定的编译时索引

访问其元素
a[5]; a[8]; a[23];

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

a[i];

i的值不固定,它可以在运行时更改,因此允许您在运行时选择数组的不同元素。这与指向成员函数类型的指针非常类似。

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

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

答案 8 :(得分:2)

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

不是你每天都在使用的东西......

答案 9 :(得分:2)

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

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

如果您有很多不同的选项,跳转表可以更清晰地安排您的代码......

尽管如此,这取决于个人偏好。无论如何,Switch语句和跳转表对应的代码大致相同:)

答案 10 :(得分:2)

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

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

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

答案 11 :(得分:1)

This article描述了一些指向成员函数的指针很有用的场景。

答案 12 :(得分:1)

  

要调用它,您需要对实例的引用,但是您可以直接调用func&amp;不需要指向它的指针。

这完全没有意义。这里有两个独立的问题:

  • 稍后会采取什么行动
  • 上执行该操作的对象

对实例的引用满足第二个要求。指向成员函数的指针解决了第一个问题:它们是一种非常直接的记录方式 - 在程序执行的某个阶段 - 应该在执行的某个后期执行,可能由程序的另一部分执行。

实施例

假设你有一只可以吻人或搔痒的猴子。下午6点,你的程序应该让猴子松散,并知道猴子应该去哪些人,但是下午3点左右,你的用户会输入应该采取的行动。

初学者的方法

所以,下午3点你可以设置一个变量“enum Action {Kiss,Tickle} action;”,然后在下午6点你可以做一些像“if(action == Kiss)monkey-&gt; kiss(person); else else monkey-&GT;发痒(人)”

问题

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

成员函数指针

更好的方法是使用更专业的varibale - 成员函数指针 - 直接记录下午6点要执行的操作。这就是成员函数指针。这是一个先前设定的吻或痒痒选择器,为猴子创造一个“状态” - 它是一个搔痒者还是一个接吻者 - 可以在以后使用。后面的代码只调用已经设置的任何函数,而不必考虑可能性或者有if / else-if或switch语句。

  

要调用它,您需要对实例的引用,但是您可以直接调用func&amp;不需要指向它的指针。

回到这个。所以,如果您决定在编译时采取哪种操作(即程序中的X点,它肯定是一个痒痒),这是很好的。函数指针适用于您不确定的情况,并希望将操作设置与这些操作的调用分离。

相关问题