C++ 对非静态方法的回调

C++ 对非静态方法的回调,c++,opengl,glut,C++,Opengl,Glut,想想你的基本过剩计划。它们只是从一个main方法运行,并包含像'glutMouseFunc(MouseButton)这样的回调,其中MouseButton是一个方法的名称 我所做的是将主文件封装到一个类中,这样MouseButton就不再是一个静态函数,而是一个实例。但这样做会导致编译错误: 错误2错误C3867:'StartHand::MouseButton':函数调用缺少参数列表;使用“&StartHand::MouseButton”创建指向成员c:\users\angeleyes\docu

想想你的基本过剩计划。它们只是从一个main方法运行,并包含像'glutMouseFunc(MouseButton)这样的回调,其中MouseButton是一个方法的名称

我所做的是将主文件封装到一个类中,这样MouseButton就不再是一个静态函数,而是一个实例。但这样做会导致编译错误:

错误2错误C3867:'StartHand::MouseButton':函数调用缺少参数列表;使用“&StartHand::MouseButton”创建指向成员c:\users\angeleyes\documents\visual studio 2008\projects\capstone ver 4\StartHand.cpp 388 IK引擎的指针

由于类非常庞大,因此不可能提供代码示例


我尝试过使用
this->MouseButton
,但也出现了同样的错误。不能为回调提供指向实例函数的指针吗?

正如错误消息所说,必须使用
&StartHand::MouseButton
语法获取指向成员函数(ptmf)的指针;这只是语言的一部分

在使用ptmf时,您正在调用的函数glutMouseFunc(在本例中)也必须期望获得ptmf作为回调,否则使用非静态MouseButton将无法工作。相反,一种常见的技术是回调使用用户提供的
void*
上下文,该上下文可以是实例指针,但执行回调的库必须明确允许此参数。确保与外部库(下面的handle\u鼠标函数)期望的ABI匹配也是很重要的

由于glut不允许用户提供上下文,所以必须使用另一种机制:将对象与glut的当前窗口相关联。但是,它确实提供了一种获取“当前窗口”的方法,我使用它将
void*
与窗口关联起来。然后您只需要创建一个函数来进行类型转换并调用该方法

机械:

#include <map>

int glutGetWindow() { return 0; } // make this example compile and run  ##E##

typedef std::pair<void*, void (*)(void*,int,int,int,int)> MouseCallback;
typedef std::map<int, MouseCallback> MouseCallbacks;
MouseCallbacks mouse_callbacks;
extern "C" void handle_mouse(int button, int state, int x, int y) {
  MouseCallbacks::iterator i = mouse_callbacks.find(glutGetWindow());
  if (i != mouse_callbacks.end()) { // should always be true, but possibly not
                                    // if deregistering and events arrive
    i->second.second(i->second.first, button, state, x, y);
  }
}

void set_mousefunc(
  MouseCallback::first_type obj,
  MouseCallback::second_type f
) {
  assert(obj); // preconditions
  assert(f);
  mouse_callbacks[glutGetWindow()] = MouseCallback(obj, f);
  //glutMouseFunc(handle_mouse); // uncomment in non-example  ##E##
  handle_mouse(0, 0, 0, 0); // pretend it's triggered immediately  ##E##
}

void unset_mousefunc() {
  MouseCallbacks::iterator i = mouse_callbacks.find(glutGetWindow());
  if (i != mouse_callbacks.end()) {
    mouse_callbacks.erase(i);
    //glutMouseFunc(0); // uncomment in non-example  ##E##
  }
}
#包括
int glutGetWindow(){return 0;}//使此示例编译并运行###E##
typedef std::对鼠标回调;
typedef std::映射鼠标回调;
鼠标回调鼠标回调;
外部“C”无效句柄鼠标(int按钮、int状态、int x、int y){
鼠标回调::迭代器i=mouse_callbacks.find(glutGetWindow());
if(i!=mouse\u callbacks.end()){//应该始终为true,但可能不是
//如果取消注册和事件到达
i->second.second(i->second.first,按钮,状态,x,y);
}
}
无效集_mousefunc(
MouseCallback::第一种类型的obj,
MouseCallback::第二种类型f
) {
断言(obj);//前提条件
断言(f);
鼠标回调[glutGetWindow()]=MouseCallback(obj,f);
//glutMouseFunc(handle_mouse);//在非示例##E中取消注释##
控制鼠标(0,0,0,0);//假装它立即被触发##
}
void unset_mousefunc(){
鼠标回调::迭代器i=mouse_callbacks.find(glutGetWindow());
if(i!=mouse\u callbacks.end()){
鼠标返回。擦除(i);
//glutMouseFunc(0);//在非示例##E中取消注释##
}
}
例如:

#include <iostream>

struct Example {
  void MouseButton(int button, int state, int x, int y) {
    std::cout << "callback\n";
  }
  static void MouseButtonCallback(
    void* self, int button, int state, int x, int y
  ) {
    static_cast<Example*>(self)->MouseButton(button, state, x, y);
  }
};

int main() {
  Example obj;
  set_mousefunc(&obj, &Example::MouseButtonCallback);

  return 0;
}
#包括
结构示例{
无效鼠标按钮(int按钮、int状态、int x、int y){
标准:鼠标按钮(按钮、状态、x、y);
}
};
int main(){
示例obj;
set_mousefunc(&obj,&Example::MouseButtonCallback);
返回0;
}
注意,您不再直接调用glutMouseFunc;它作为[un]set_mousefunc的一部分进行管理



以防不清楚:我已经重写了这个答案,所以它应该适合您,从而避免了C/C++链接问题。它将按原样编译和运行(没有glut),并且应该使用glut,只需稍加修改:注释或取消注释标记为
###E#

的4行。您不能将静态回调替换为实例回调。当调用者调用你的回调时,它调用的是什么实例?换句话说,调用方如何传递正式的“this”参数

解决方案是使用静态回调存根并将实例作为参数传递,这意味着被调用方必须接受在调用回调时将传回的任意pvoid。在存根中,您可以调用非静态方法:

class C {
    void f() {...}
    static void F(void* p) {
      C* pC = (C*)p;
      pC->f();
    }
  }

  C* pC = ...;
  someComponent.setCallback(&C::F, pC);

不,不能将指向实例函数的指针提供给期望具有特定签名的函数指针的回调函数。他们的签名不同。它不会编译


通常,这样的API允许您将void*作为“上下文”参数传递。在那里传入对象,然后编写一个包装器函数,该函数将上下文作为回调。包装器将其转换回您正在使用的任何类,并调用相应的成员函数。

在这种情况下,您不能使用非静态成员函数。 glutMouseFunc期望的参数类型基本上是

void (*)(int, int, int, int)
而非静态成员函数的类型为

void (StartHand::*)(int, int, int, int)
第一个问题是类型并不真正匹配。 其次,为了能够调用该方法,回调函数必须知道你的方法属于哪个对象(即“this”指针)(这就是为什么这些类型在一开始是不同的)。 第三,我认为您使用了错误的语法来检索方法的指针。正确的语法应该是:&StartHand::MouseButton


因此,您必须将该方法设置为静态方法,或者使用其他静态方法,该方法将知道调用MouseButton时使用的起始点和指针。

与大家似乎所说的相反,您完全可以使用非静态成员函数作为回调方法。它需要专门为获取指向非静态成员的指针而设计的特殊语法,以及在类的特定实例上调用该函数的特殊语法。有关所需语法的讨论,请参阅

下面是示例代码,说明了这是如何工作的:

#include <cstdlib>
#include <string>
#include <iostream>
#include <vector>
#include <sstream>
#include <algorithm>
using namespace std;


class Operational
{
public:
    Operational(int value) : value_(value) {};

    string FormatValue() const ;

private:
    int value_;

};

string Operational::FormatValue() const
{
    stringstream ss;
    ss << "My value is " << value_;
    return ss.str();
}

typedef string(Operational::*FormatFn)() const; // note the funky syntax

Operational make_oper(int val)
{
    return Operational(val);
}

int main()
{
    // build the list of objects with the instance callbacks we want to call
    Operational ops[] = {1, 2, 3, 5, 8, 13};
    size_t numOps = sizeof(ops)/sizeof(ops[0]);

    // now call the instance callbacks
    for( size_t i = 0; i < numOps; ++i )
    {
        // get the function pointer
        FormatFn fn = &Operational::FormatValue;    

        // get a pointer to the instance
        Operational* op = &ops[i];  

        // call the callback on the instance
        string retval = (op->*fn)();

        // display the output
        cout << "The object @ " << hex << (void*)op << " said: '" << retval << "'" << endl;
    }


    return 0;
}

下面的工作在C++中定义了一个C回调函数,例如使用GLUT(GLUDISPLUNFC,GLUBEBOBOAR)

The object @ 0017F938 said: 'My value is 1' 
The object @ 0017F93C said: 'My value is 2' 
The object @ 0017F940 said: 'My value is 3' 
The object @ 0017F944 said: 'My value is 5' 
The object @ 0017F948 said: 'My value is 8' 
The object @ 0017F94C said: 'My value is 13'
MyClass * ptr_global_instance = NULL;

extern "C" void mouse_buttons_callback(int button, int state, int x, int y) {

    // c function call which calls your c++ class method

    ptr_global_instance->mouse_buttons_cb(button, state, x, y);
}

void MyClass::mouse_buttons_cb(int button, int state, int x, int y) {

    // this is actual body of callback - ie.  if (button == GLUT_LEFT_BUTTON) ...
    // implemented as a c++ method
}

void MyClass::setup_glut(int argc, char** argv) { // largely boilerplate glut setup

    glutInit(&argc, argv);

    // ... the usual suspects go here like glutInitWindowSize(900, 800); ...

    setupMouseButtonCallback(); // <-- custom linkage of c++ to cb

    // ... other glut setup calls here
}

void MyClass::setupMouseButtonCallback() {

    // c++ method which registers c function callback

    ::ptr_global_instance = this;
    ::glutMouseFunc(::mouse_buttons_callback);
}
void mouse_buttons_cb(int button, int state, int x, int y);

void setupMouseButtonCallback();