【问题标题】:Function pointer to a non-static member function when the class type is unknown?类类型未知时指向非静态成员函数的函数指针?
【发布时间】:2025-12-24 23:40:11
【问题描述】:

我正在开发一个游戏项目,该项目具有渲染到 opengl 上下文中的临时构建控件;诸如按钮、滚动条、列表框等之类的东西。其中许多控件是嵌套的;比如我的列表框有一个滚动条,一个滚动条有3个按钮,等等。

当滚动条更改值时,我希望它调用响应更改的“某些”函数(通常在它的父对象中)。例如,如果列表框有一个滑块,它应该实例化滑块,然后告诉新滑块它应该调用列表框的“onScroll(float)”函数。所有控件共享一个公共基类,所以我可以有一个“base* parent”父指针,然后执行“parent->onScroll(val)”。但问题是当父母没有从基地继承时会发生什么;没有虚拟的 onScroll() 可以执行,因此*父级必须定期检查是否有任何子控件更改了值。这也会使其他控件变得混乱,因为它们甚至可能没有子控件,或者可能需要不同的事件类型,例如选择列表条目对象时等。

更好的解决方案是让子对象维护一个通用函数指针(如回调),它可以由父对象设置,并在必要时由子对象调用。像这样的:

typedef (*ptFuncF)(float);

class glBase {
public:
  //position,isVisible,virtual mouseDown(x,y),etc
};

class glDerivedChild : public glBase {
public:
  glDerivedChild();
  ~glDerivedChild();

  void changeValue(float fIn) {
    Value = fIn; //ignore these forward declaration errors
    (*callBack)(fIn);
  }

  void setCallBack(ptFuncF pIn) {callBack = pIn;}

  ptFuncF callBack;
  float Value;
};

class glDerivedParent : public glBase {
public:
  glDerivedParent() {
    child = new glDerivedChild();
    child->setCallBack(&onScroll);
  }
  ~glDerivedParent() {delete child;}

  void onScroll(float fIn) {
    //do something
  }

  glDerivedChild* child;
};

class someFoo {
public:
  someFoo() {
    child->setCallBack(&setValue);
  }

  void setValue(float fIn) {
    //do something else
  }

  glDerivedChild child;
};

我对函数指针有点陌生,所以我知道我(显然)做错了很多事情。我怀疑它可能涉及“typedef (glBase::*ptFuncF)(float);”之类的东西'onScroll(f)' 是一个被覆盖的虚函数,可能具有像 'virtual void childCallBack(float)' 这样的通用名称。我更愿意让解决方案尽可能接近原版,所以我想避免像 boost 这样的外部库。在 8 小时的大部分时间里,我一直在为这个问题挠头,我希望有人能提供帮助。谢谢!

【问题讨论】:

  • 你试过typedef (glBase::*ptFuncF)(float);吗?发生了什么?
  • 问题是这似乎只适用于静态成员函数,我需要它来调用特定于类实例的函数。
  • 这不是真的。让我发布作为答案。

标签: c++ inheritance callback function-pointers


【解决方案1】:

我认为,您想要的是某种事件或信号机制。

例如,您可以研究在Windows 上如何组织事件处理。简而言之,您的滚动条会在系统中生成新事件,然后系统将其传播到系统中注册的所有元素。

更方便的机制是信号/槽机制。 BoostQt 提供此类工具。 我会推荐这个解决方案

但如果你仍然想使用只是回调,我会推荐使用std::function (boost::function)(在需要时与std::bind (boost::bind) 结合使用)而不是原始函数指针。

【讨论】:

  • 我目前正在使用 qt,但由于开销和额外的 dll,我计划最终删除大部分 qt 内容;例如,我打算稍后用 GLUT 替换我现在使用的 QGLWidget。由于这些控件主要与显示相关,因此我可能可以使用指向父级的基类指针,然后调用合适的虚函数。至于与主小部件的交互,基类应该足够小,以便我可以使用多重继承并通过这种方式对其进行修改。我只是希望函数指针不必如此特定于类型......
【解决方案2】:

使用boost::function(或std::function,如果可用)。像这样(使用你的符号):

typedef std::function<void (float)> ptFuncF; 
//...
void setCallBack(const ptFuncF &pIn);
//...
child->setCallBack(std::bind(&glDerivedParent::onScroll, this, _1)); 
//...
child->setCallBack(std::bind(&someFoo::setValue, this, _1)); 

【讨论】:

    【解决方案3】:

    指向类的成员函数的函数指针具有这样的类型:

    <return type> (<class name>::*)(<arguments>)
    

    例如:

    typedef void (glBase::*ptFuncF)(float);
            ^^^^
          by the way, you have forgot the `void` in your `typedef`
    
    ptFuncF func = &glDerivedChild::onScroll;
    

    你可以这样使用它:

    glDerivedChild c;
    (c.*func)(1.2);
    

    在您的特定示例中,该函数是派生类本身的成员,因此您应该这样称呼它:

    (c.*c.callback)(1.2);
    

    内部c.callback 是函数指针。其余的和上面一模一样,也就是:

    (class_instance.*function_pointer)(arguments);
    

    您可能还想看看this question

    【讨论】:

      【解决方案4】:

      好的,我想出的解决方法有一些额外的开销和分支,但在其他方面是合理的。

      基本上,每个回调函数都实现为一个虚拟成员函数,它接收所需的参数,包括指向进行调用的对象的void* 指针。每个派生对象还有一个基类指针,该指针指向应该接收它发出的任何事件的对象(通常是它的父对象,但可以是继承自基类的任何对象)。如果控件有多个子控件,回调函数使用void* 指针来区分它们。这是一个例子:

      class glBase {
      public:
        virtual onChildCallback(float fIn, void* caller);
      
        glBase* parent;
      };
      
      class glSlider : public glBase {
      public:
        glSlider(glBase* parentIn);
      
        void changeValue(float fIn) {
          Value = fIn;
          parent->onChildCallback(fIn, this);
        }
      
        float Value;
      };
      
      class glButton : public glBase {
      public:
        glButton(glBase* parentIn);
      
        void onClick() {
          parent->onChildCallback(0, this);
        }
      };
      
      class glParent : public glBase {
      public:
        glParent(glBase* parentIn) : parent(parentIn) {
          childA = new glSlider(this);
          childB = new glButton(this);
        }
      
        void onChildCallback(float fIn, void* caller) {
          if (caller == childA) {
            //slider specific actions
          } else if (caller == childB) {
            //button specific actions
          } else {
            //generic actions
          }
        }
      
        glSlider* childA;
        glButton* childB;
      };
      

      除了相当少量的开销外,该方案足够灵活,派生类可以忽略某些组件或完全省略它们。稍后我可能会回到函数指针的想法(感谢 shahbaz),但无论如何,两种方案的一半基础架构都是相同的,额外的开销很小,特别是因为控件的数量和种类都相当少。让回调函数使用嵌套响应实际上会更好一些,因为您不需要为每个子对象(例如 onUpButton、onDownButton 等)使用单独的函数。

      【讨论】: