libreria - opengl 3d example c++
Devolución de llamada al método no estático (6)
Lo siguiente funciona en c ++ para definir la función de devolución de llamada de ca, útil por ejemplo cuando se usa glut (glutDisplayFunc, glutKeyboardFunc, glutMouseFunc ...) cuando solo se necesita una sola instancia de esta clase:
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);
}
En su encabezado de MyClass, agregamos:
void mouse_buttons_cb(int button, int state, int x, int y);
void setupMouseButtonCallback();
Esto también funciona usando flujos lógicos idénticos para configurar su llamada de saturación a glutDisplayFunc (pantalla)
Piensa en tus programas GLUT básicos. Simplemente se ejecutan desde un método principal y contienen devoluciones de llamadas como `glutMouseFunc (MouseButton) donde MouseButton es el nombre de un método.
Lo que he hecho es que he encapsulado el archivo principal en una clase, de modo que MouseButton ya no es una función estática, sino que tiene una instancia. Pero hacer esto me da un error de compilación:
Error 2 error C3867: ''StartHand :: MouseButton'': llamada a la función que falta la lista de argumentos; use ''& StartHand :: MouseButton'' para crear un puntero al miembro c: / users / angeleyes / documents / visual studio 2008 / projects / capstone ver 4 / starthand.cpp 388 IK Engine
No es posible proporcionar una muestra de código ya que la clase es bastante grande.
He intentado usar this->MouseButton
pero eso da el mismo error. ¿No se puede dar un puntero a una función de instancia para la devolución de llamada?
Contrariamente a lo que todos parecen decir, definitivamente PUEDES usar una función de miembro no estático como método de devolución de llamada. Requiere una sintaxis especial diseñada específicamente para obtener punteros a miembros no estáticos y una sintaxis especial para llamar a esa función en una instancia específica de una clase. Vea aquí para una discusión de la sintaxis necesaria.
Aquí hay un código de muestra que ilustra cómo funciona esto:
#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;
}
La salida de este programa cuando lo ejecuté en mi máquina fue:
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''
No puede reemplazar una devolución de llamada estática con una instancia. Cuando la persona que llama llama su devolución de llamada, ¿en qué instancia llama? En otras palabras, ¿cómo pasa la persona que llama en el argumento formal de "esto"?
La solución es tener un código de retorno de llamada estático y pasar la instancia como argumento, lo que implica que el destinatario debe aceptar un pvoid arbitrario que pasará de nuevo al invocar la devolución de llamada. En el código auxiliar, puede llamar al método no estático:
class C {
void f() {...}
static void F(void* p) {
C* pC = (C*)p;
pC->f();
}
}
C* pC = ...;
someComponent.setCallback(&C::F, pC);
No puede usar una función miembro no estática en este caso. Básicamente, el tipo de argumento esperado por glutMouseFunc es
void (*)(int, int, int, int)
mientras que el tipo de su función miembro no estática es
void (StartHand::*)(int, int, int, int)
El primer problema es que los tipos realmente no coinciden. En segundo lugar, para poder llamar a ese método, la devolución de llamada debería saber a qué objeto (es decir, "este" puntero) pertenece su método (por eso, en gran parte, los tipos son diferentes). Y tercero, creo que estás usando la sintaxis incorrecta para recuperar el puntero del método. La sintaxis correcta debería ser: & StartHand :: MouseButton.
Entonces, tienes que hacer que el método sea estático o usar algún otro método estático que sepa qué puntero de StartHand usar para llamar a MouseButton.
No, no se puede dar un puntero a una función de instancia a una función de devolución de llamada que espera un puntero de función de una determinada firma. Sus firmas son diferentes. No compilará
En general, tales API le permiten pasar un vacío * como un parámetro de "contexto". Usted transfiere su objeto allí y escribe una función de envoltura que toma el contexto como devolución de llamada. El contenedor lo devuelve a la clase que estaba usando y llama a la función miembro correspondiente.
Como dice el mensaje de error, debe usar la &StartHand::MouseButton
para obtener un puntero a una función miembro (ptmf); esto es simplemente obligatorio como parte del lenguaje.
Cuando se utiliza un ptmf, la función que está llamando, glutMouseFunc en este caso, también debe esperar obtener un ptmf como devolución de llamada, de lo contrario, no funcionará con MouseButton no estático. En cambio, una técnica común es que las devoluciones de llamada funcionen con un contexto void*
proporcionado por el usuario, que puede ser el puntero de instancia, pero la biblioteca que realiza las devoluciones de llamada debe permitir explícitamente este parámetro. También es importante asegurarse de que coincida con el ABI esperado por la biblioteca externa (la función handle_mouse a continuación).
Como el exceso no permite el contexto proporcionado por el usuario, debe usar otro mecanismo: asociar los objetos con la ventana actual de exceso. Sin embargo, proporciona una forma de obtener la "ventana actual" y la he usado para asociar un void*
con la ventana. Entonces simplemente necesita crear un trampolín para hacer la conversión de tipo y llamar al método.
Maquinaria:
#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##
}
}
Ejemplo:
#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;
}
Observe que ya no llama a glutMouseFunc directamente; se gestiona como parte de [un] set_mousefunc .
En caso de que no esté claro: he reescrito esta respuesta, por lo que debería funcionar para usted y para evitar el problema de vinculación C / C ++ debatido. Se compilará y ejecutará como está (sin exceso), y debería funcionar con saturación con solo modificaciones menores: comente o elimine las 4 líneas marcadas con ##E##
.