Обратный вызов к нестатическому методу - PullRequest
2 голосов
/ 14 января 2010

Подумайте о ваших основных программах GLUT. Они просто запускаются из основного метода и содержат обратные вызовы, такие как `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 Engine

Невозможно предоставить пример кода, так как класс довольно большой.

Я пытался использовать this->MouseButton, но это выдает ту же ошибку. Разве нельзя указать указатель на функцию экземпляра для обратного вызова?

Ответы [ 6 ]

5 голосов
/ 15 января 2010

Как говорится в сообщении об ошибке, вы должны использовать синтаксис &StartHand::MouseButton, чтобы получить указатель на функцию-член (ptmf); это просто как часть языка.

При использовании ptmf функция, которую вы вызываете, в данном случае glutMouseFunc, также должна ожидать получения ptmf в качестве обратного вызова, в противном случае использование нестатической MouseButton не будет работать. Вместо этого общепринятым методом для обратных вызовов является работа с предоставленным пользователем контекстом void*, который может быть указателем экземпляра - но библиотека, выполняющая обратные вызовы, должна явно разрешить этот параметр. Также важно убедиться, что вы соответствуете ABI, ожидаемому от внешней библиотеки (функция handle_mouse ниже).

Так как 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##
  }
}

Пример:

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

Обратите внимание, что вы больше не вызываете glutMouseFunc напрямую; он управляется как часть [un] set_mousefunc .


На всякий случай, если неясно: я переписал этот ответ, чтобы он работал для вас и чтобы избежать обсуждения вопроса о связях C / C ++. Он будет компилироваться и запускаться как есть (без переизбытка), и он должен работать с переизбытком с незначительными изменениями: прокомментировать или раскомментировать 4 строки, помеченные ##E##.

2 голосов
/ 15 января 2010

Нет, указатель на функцию экземпляра не может быть передан функции обратного вызова, ожидающей указатель функции определенной сигнатуры. Их подписи разные. Он не скомпилируется.

Как правило, такие API позволяют передавать void * в качестве параметра "context". Вы передаете туда свой объект и пишете функцию-обертку, которая принимает контекст в качестве обратного вызова. Оболочка возвращает его к тому классу, который вы использовали, и вызывает соответствующую функцию-член.

1 голос
/ 15 января 2010

Вопреки тому, что, кажется, все говорят, вы определенно МОЖЕТЕ использовать нестатическую функцию-член в качестве метода обратного вызова. Требуется специальный синтаксис, разработанный специально для получения указателей на нестатические члены, и специальный синтаксис для вызова этой функции в конкретном экземпляре класса. См. здесь для обсуждения необходимого синтаксиса.

Вот пример кода, который иллюстрирует, как это работает:

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

Вывод этой программы при запуске на моей машине был:

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'
1 голос
/ 15 января 2010

Вы не можете заменить статический обратный вызов экземпляром. Когда вызывающий абонент вызывает ваш обратный вызов, в какой момент он звонит? Другими словами, как вызывающая сторона передает формальный аргумент this?

Решение состоит в том, чтобы иметь статическую заглушку обратного вызова и передавать экземпляр в качестве аргумента, что подразумевает, что вызываемый объект должен принять произвольный pvoid, который будет возвращаться при вызове обратного вызова. В заглушке вы можете вызвать нестатический метод:

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

  C* pC = ...;
  someComponent.setCallback(&C::F, pC);
0 голосов
/ 08 июля 2013

Следующее работает в c ++ для определения функции обратного вызова c, полезно, например, при использовании glut (glutDisplayFunc, glutKeyboardFunc, glutMouseFunc ...), когда вам нужен только один экземпляр этого класса:

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

В заголовок MyClass добавляем:

void mouse_buttons_cb(int button, int state, int x, int y);

void setupMouseButtonCallback();

Это также работает с использованием идентичных логических потоков для настройки вашего перенасыщения. вызов glutDisplayFunc (отображение)

0 голосов
/ 15 января 2010

В этом случае нельзя использовать нестатическую функцию-член. В основном тип аргумента, ожидаемого glutMouseFunc, равен

void (*)(int, int, int, int)

в то время как тип вашей нестатической функции-члена

void (StartHand::*)(int, int, int, int)

Первая проблема заключается в том, что типы не совпадают. Во-вторых, чтобы иметь возможность вызывать этот метод, обратный вызов должен знать, к какому объекту (то есть указателю "this") принадлежит ваш метод (именно поэтому типы в первую очередь отличаются). И в-третьих, я думаю, что вы используете неправильный синтаксис для получения указателя метода. Правильный синтаксис должен быть: & StartHand :: MouseButton.

Итак, вы должны либо сделать этот метод статическим, либо использовать какой-то другой статический метод, который бы знал, какой указатель StartHand использовать для вызова MouseButton.

...