-1

我正在开发一个游戏项目,该项目具有渲染到 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 小时的大部分时间里,我一直在摸索这个问题,我希望有人能提供帮助。谢谢!

4

4 回答 4

1

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

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

您可能还想看看这个问题

于 2012-07-08T16:32:30.517 回答
1

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

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

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

但是,如果您仍想仅使用回调,我建议您使用std::function ( boost::function )(在需要时与std::bind ( boost::bind ) 结合使用)而不是原始函数指针。

于 2012-07-08T11:14:14.430 回答
1

使用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)); 
于 2012-07-08T11:14:32.497 回答
-1

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

基本上,每个回调函数都被实现为一个虚拟成员函数,它接收所需的参数,包括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 等)使用单独的函数。

于 2012-07-08T19:02:20.677 回答