c++ - switch - ¿Cómo puedo pasar una función miembro de la clase como una devolución de llamada?
visual studio 2017 community español (8)
¿Es m_cRedundencyManager
capaz de usar funciones miembro? La mayoría de las devoluciones de llamadas están configuradas para usar funciones regulares o funciones de miembros estáticos. Eche un vistazo a esta página en C ++ FAQ Lite para más información.
Actualización: la declaración de la función que usted proporcionó muestra que m_cRedundencyManager
espera una función del formulario: void yourCallbackFunction(int, void *)
. Por lo tanto, las funciones de los miembros son inaceptables como devoluciones de llamada en este caso. Una función miembro estática puede funcionar, pero si eso no es aceptable en su caso, el siguiente código también funcionaría. Tenga en cuenta que utiliza un molde malvado de void *
.
// in your CLoggersInfra constructor:
m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this);
// in your CLoggersInfra header:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr);
// in your CLoggersInfra source file:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr)
{
((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i);
}
Estoy usando una API que me exige pasar un puntero a la función como devolución de llamada. Intento usar esta API de mi clase pero obtengo errores de compilación.
Esto es lo que hice de mi constructor:
m_cRedundencyManager->Init(this->RedundencyManagerCallBack);
Esto no compila - Recibo el siguiente error:
Error 8 error C3867: ''CLoggersInfra :: RedundencyManagerCallBack'': llamada a la función lista de argumentos faltantes; use ''& CLoggersInfra :: RedundencyManagerCallBack'' para crear un puntero al miembro
Intenté la sugerencia de usar &CLoggersInfra::RedundencyManagerCallBack
- no funcionó para mí.
¿Alguna sugerencia / explicación para esto?
Estoy usando VS2008.
¡¡Gracias!!
¿Qué argumento toma Init
? ¿Cuál es el nuevo mensaje de error?
Los punteros de método en C ++ son un poco difíciles de usar. Además del puntero de método en sí mismo, también debe proporcionar un puntero de instancia (en su caso this
). ¿Quizás Init
espera como un argumento separado?
Como ahora demostró, su función init acepta una función que no es miembro. hazlo así:
static void Callback(int other_arg, void * this_pointer) {
CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer);
self->RedundencyManagerCallBack(other_arg);
}
y llame a Init con
m_cRedundencyManager->Init(&CLoggersInfra::Callback, this);
Eso funciona porque un puntero de función a una función miembro estática no es un puntero de función miembro y, por lo tanto, puede manejarse como un simple puntero a una función libre.
Esta pregunta y respuesta de C ++ FAQ Lite cubre su pregunta y las consideraciones involucradas en la respuesta bastante bien, creo. Breve fragmento de la página web que he vinculado:
No lo hagas
Como una función miembro no tiene sentido sin un objeto para invocarla, no puede hacer esto directamente (si The X Window System se reescribió en C ++, probablemente pasaría referencias a objetos a su alrededor, no solo punteros a funciones; encarnaría la función requerida y probablemente mucho más).
Esta es una pregunta simple, pero la respuesta es sorprendentemente compleja. La respuesta corta es que puede hacer lo que está tratando de hacer con std :: bind1st o boost :: bind. La respuesta más larga está abajo.
El compilador es correcto para sugerir que use & CLoggersInfra :: RedundencyManagerCallBack. Primero, si RedundencyManagerCallBack es una función miembro, la función en sí no pertenece a ninguna instancia particular de la clase CLoggersInfra. Pertenece a la clase misma. Si alguna vez ha llamado a una función de clase estática, puede haber notado que usa la misma sintaxis SomeClass :: SomeMemberFunction. Dado que la función en sí misma es "estática" en el sentido de que pertenece a la clase en lugar de a una instancia particular, utiliza la misma sintaxis. El ''&'' es necesario porque, técnicamente hablando, no se transfieren funciones directamente; las funciones no son objetos reales en C ++. En su lugar, técnicamente está pasando la dirección de memoria para la función, es decir, un puntero al lugar donde comienzan las instrucciones de la función en la memoria. Sin embargo, la consecuencia es la misma: está efectivamente ''pasando una función'' como parámetro.
Pero eso es solo la mitad del problema en este caso. Como dije, RedundencyManagerCallBack la función no ''pertenece'' a ninguna instancia en particular. Pero parece que quieres pasarlo como una devolución de llamada con una instancia particular en mente. Para comprender cómo hacer esto, necesita comprender qué funciones de miembro realmente son: funciones regulares no definidas en cualquier clase con un parámetro oculto adicional.
Por ejemplo:
class A {
public:
A() : data(0) {}
void foo(int addToData) { this->data += addToData; }
int data;
};
...
A an_a_object;
an_a_object.foo(5);
A::foo(&an_a_object, 5); // This is the same as the line above!
std::cout
¿Cuántos parámetros toma A :: foo? Normalmente diríamos 1. Pero bajo el capó, foo realmente toma 2. En cuanto a la definición de A :: foo, necesita una instancia específica de A para que el puntero ''este'' sea significativo (el compilador necesita saber qué '' esto es). La forma en que suele especificar lo que quiere que sea ''esto'' es a través de la sintaxis MyObject.MyMemberFunction (). Pero esto es solo azúcar sintáctica para pasar la dirección de MyObject como primer parámetro a MyMemberFunction. De manera similar, cuando declaramos las funciones de los miembros dentro de las definiciones de las clases, no ponemos "esto" en la lista de parámetros, pero esto es solo un regalo de los diseñadores de idiomas para guardar la escritura. En su lugar, debe especificar que una función miembro es estática para inhabilitarla y obtener automáticamente el parámetro ''this'' adicional. Si el compilador de C ++ tradujo el ejemplo anterior al código C (el compilador original de C ++ funcionaba de esa manera), probablemente escribiría algo como esto:
struct A { int data; }; void a_init(A* to_init) { to_init->data = 0; } void a_foo(A* this, int addToData) { this->data += addToData; } ... A an_a_object; a_init(0); // Before constructor call was implicit a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5);
Volviendo a su ejemplo, ahora hay un problema obvio. ''Init'' quiere un puntero a una función que toma un parámetro. Pero & CLoggersInfra :: RedundencyManagerCallBack es un puntero a una función que toma dos parámetros, su parámetro normal y el parámetro secreto ''this''. Entonces, ¿por qué todavía está obteniendo un error de compilación? (Como nota al margen: si alguna vez ha usado Python, este tipo de confusión es por qué se requiere un parámetro ''self'' para todas las funciones de los miembros).
La forma más detallada de manejar esto es crear un objeto especial que contiene un puntero a la instancia que desea y tiene una función miembro llamada algo así como ''ejecutar'' o ''ejecutar'' (o sobrecarga el operador ''()'') que toma los parámetros para la función miembro, y simplemente llama a la función miembro con esos parámetros en la instancia almacenada. Pero esto requeriría que cambies ''Init'' para tomar tu objeto especial en lugar de un puntero de función sin formato, y parece que Init es el código de otra persona. Y hacer una clase especial por cada vez que surja este problema dará lugar a la inflamación del código.
Así que ahora, finalmente, la buena solución, función boost :: bind and boost ::, la documentación de cada uno puede encontrar aquí:
boost :: bind docs , boost :: function docs
boost :: bind le permitirá tomar una función y un parámetro para esa función y crear una nueva función donde ese parámetro esté ''bloqueado'' en su lugar. Entonces, si tengo una función que agrega dos enteros, puedo usar boost :: bind para hacer una nueva función donde uno de los parámetros está bloqueado para decir 5. Esta nueva función solo tomará un parámetro entero, y siempre agregará 5 específicamente lo. Usando esta técnica, puede ''bloquear'' el parámetro oculto ''this'' para que sea una instancia de clase particular, y generar una nueva función que solo tome un parámetro, tal como desee (tenga en cuenta que el parámetro oculto es siempre el primer parámetro, y los parámetros normales vienen en orden después de esto). Mire los documentos boost :: bind para ver ejemplos, incluso discuten específicamente su uso para funciones de miembros. Técnicamente hay una función estándar llamada std :: bind1st que también puedes usar, pero boost :: bind es más general.
Por supuesto, solo hay una captura más. boost :: bind hará una función de boost :: agradable para usted, pero esto técnicamente aún no es un puntero de función crudo como probablemente lo quiere Init. Afortunadamente, boost proporciona una forma de convertir boost :: function en punteros crudos, como se documenta en here . Cómo implementa esto está más allá del alcance de esta respuesta, aunque también es interesante.
No se preocupe si esto parece ridículamente difícil: su pregunta intercepta varias de las esquinas más oscuras de C ++, y boost :: bind es increíblemente útil una vez que la aprende.
Esta respuesta es una respuesta a un comentario anterior y no funciona con VisualStudio 2008, pero debería ser preferible con compiladores más recientes.
Mientras tanto, ya no es necesario usar un puntero void y tampoco hay necesidad de aumentar, ya que std::bind
y std::function
están disponibles. Una ventaja (en comparación con los punteros vacíos) es la seguridad del tipo ya que el tipo de retorno y los argumentos se establecen explícitamente usando std::function
:
// std::function<return_type(list of argument_type(s))>
void Init(std::function<void(void)> f);
Luego puede crear el puntero de función con std::bind
y pasarlo a Init:
auto cLoggersInfraInstance = CLoggersInfra();
auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance);
Init(callback);
Ejemplo completo para usar std::bind
con miembro, miembros estáticos y funciones no miembro:
#include <functional>
#include <iostream>
#include <string>
class RedundencyManager // incl. Typo ;-)
{
public:
// std::function<return_type(list of argument_type(s))>
std::string Init(std::function<std::string(void)> f)
{
return f();
}
};
class CLoggersInfra
{
private:
std::string member = "Hello from non static member callback!";
public:
static std::string RedundencyManagerCallBack()
{
return "Hello from static member callback!";
}
std::string NonStaticRedundencyManagerCallBack()
{
return member;
}
};
std::string NonMemberCallBack()
{
return "Hello from non member function!";
}
int main()
{
auto instance = RedundencyManager();
auto callback1 = std::bind(&NonMemberCallBack);
std::cout << instance.Init(callback1) << "/n";
// Similar to non member function.
auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack);
std::cout << instance.Init(callback2) << "/n";
// Class instance is passed to std::bind as second argument.
// (heed that I call the constructor of CLoggersInfra)
auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack,
CLoggersInfra());
std::cout << instance.Init(callback3) << "/n";
}
Posible resultado:
Hello from non member function!
Hello from static member callback!
Hello from non static member callback!
Además, al usar std::placeholders
, puede pasar dinámicamente argumentos a la devolución de llamada (por ejemplo, esto permite el uso de return f("MyString");
en Init
si f tiene un parámetro de cadena).
Puedo ver que el init tiene la siguiente anulación:
Init (CALLBACK_FUNC_EX callback_func, void * callback_parm)
donde CALLBACK_FUNC_EX es typedef void (* CALLBACK_FUNC_EX) (int, void *);
Un puntero a una función de miembro de clase no es lo mismo que un puntero a una función. Un miembro de clase toma un argumento extra implícito ( este puntero) y usa una convención de llamada diferente.
Si su API espera una función de devolución de llamada que no sea miembro, eso es lo que tiene que pasarle.