2010-01-27 22 views
9

He creado una clase de temporizador que debe llamar a un método de devolución de llamada cuando el temporizador ha expirado. Actualmente lo tengo trabajando con punteros a funciones normales (se declaran como void (*) (void), cuando ocurre el evento transcurrido se llama al puntero a la función.Cómo definir un puntero de función de miembro general

Es posible hacer lo mismo con una función miembro que tiene también el vacío firma (AnyClass :: *) (void)

, gracias compañeros

EDIT:?. Este código tiene que trabajar en Windows y también en un sistema operativo en tiempo real (VxWorks), de modo que no utilizan bibliotecas externas sería genial.

EDIT2: Para estar seguro, lo que necesito es tener una clase de temporizador que tome una discusión en el Constructor de tipear "AnyClass.AnyMethod" sin argumentos y sin retorno. Tengo que almacenar este argumento y el último en un punto del código, solo ejecutar el método apuntado por esta variable. La esperanza es clara.

+0

¿Estás seguro de que el puntero a la función no está declarado como 'void (*) (void *)'. Me sorprendería muchísimo si no aceptara un 'void *' como un parámetro. –

Respuesta

8

Dependencias, dependencias ... sí, cierto impulso es agradable, también lo es mem_fn, pero no las necesita. Sin embargo, la sintaxis de llamar a funciones miembro es malo, por lo que un poco de magia plantilla ayuda:

class Callback 
    { 
    public: 
     void operator()() { call(); }; 
     virtual void call() = 0; 
    }; 

    class BasicCallback : public Callback 
    { 
     // pointer to member function 
     void (*function)(void); 
    public: 
     BasicCallback(void(*_function)(void)) 
      : function(_function) { }; 
     virtual void call() 
     { 
      (*function)(); 
     }; 
    }; 

    template <class AnyClass> 
    class ClassCallback : public Callback 
    { 
     // pointer to member function 
     void (AnyClass::*function)(void); 
     // pointer to object 
     AnyClass* object;   
    public: 
     ClassCallback(AnyClass* _object, void(AnyClass::*_function)(void)) 
      : object(_object), function(_function) { }; 
     virtual void call() 
     { 
      (*object.*function)(); 
     }; 
    }; 

Ahora sólo puede utilizar devolución de llamada como un mecanismo de almacenamiento de devolución de llamada así:

void set_callback(Callback* callback); 
set_callback(new ClassCallback<MyClass>(my_class, &MyClass::timer)); 

Y

Callback* callback = new ClassCallback<MyClass>(my_class, &MyClass::timer)); 

(*callback)(); 
// or... 
callback->call(); 
+0

Bien, tenga en cuenta que no entiendo casi nada aquí. C++ no es lo que uso para trabajar, solo necesito hacer algunos cambios en un proyecto en ejecución, así que espero evitar entender toda esta magia. ¿Cómo puedo ejecutar la devolución de llamada? Si acabo de poner callback(); obtengo un término no evalúa a una función tomando 0 argumentos. Gracias de antemano. –

+0

Y la clase que se ve como BasicCallback no se usa en ninguna parte. Tal vez hay un error allí? –

+0

@SoMoS, BasicCallback le permite usar la misma interfaz con sus antiguas devoluciones de llamada; no requiere una clase. Obtuvo su error porque trató de "ejecutar" un puntero, '(* callback)()' sería la llamada correcta. Modifiqué un poco mi código y agregué un método explícito de 'llamada', tal vez eso sea más claro. –

4

La mejor solución que he utilizado para ese mismo propósito era boost::signal o boost::function bibliotecas (dependiendo de si se desea una única devolución de llamada o muchos de ellos), y boost::bind para registrar las devoluciones de llamada realidad.

class X { 
public: 
    void callback() {} 
    void with_parameter(std::string const & x) {} 
}; 
int main() 
{ 
    X x1, x2; 
    boost::function< void() > callback1; 

    callback1 = boost::bind(&X::callback, &x1); 
    callback1(); // will call x1.callback() 

    boost::signal< void() > multiple_callbacks; 
    multiple_callbacks.connect(boost::bind(&X::callback, &x1)); 
    multiple_callbacks.connect(boost::bind(&X::callback, &x2)); 
    // even inject parameters: 
    multiple_callbacks.connect(boost::bind(&X::with_parameter, &x1, "Hi")); 

    multiple_callbacks(); // will call x1.callback(), x2.callback and x1.with_parameter("Hi") in turn 
} 
+1

¿Pero no es aumentar una biblioteca externa? – futureelite7

+4

Sí, no son parte del estándar, pero boost es la biblioteca no estándar más estándar que encontrará. –

+1

Además, 'function' y' bind' se agregarán a la biblioteca estándar de C++ 0x (puede que ya esté disponible en el espacio de nombres 'std :: tr1'). – UncleBens

1

boost::function parece un ajuste perfecto aquí.

+0

Dijo que no hay bibliotecas externas. –

+1

Oh, lo eché de menos :) Tenga en cuenta que la función boost :: es una biblioteca de solo cabecera, por lo que no hay dependencias de enlace. –

2

Tal vez el estándar mem_fun ya es lo suficientemente bueno para lo que quiere. Es parte de STL.

+0

Déjame revisarlo. Se ve bien. –

+0

Propuse la función boost :: ya que suponía que el temporizador sería una clase sin plantilla que tenía que mantener la devolución de llamada para su uso posterior. Como mem_fun está modelado, teplateamos la clase del temporizador en la clase de devolución de llamada, o bien tendrás que ajustar mem_fun en una clase sin plantilla, y aquí es donde terminas implementando boost :: function tú mismo ... –

0

Asumo una interfaz como esta:

void Timer::register_callback(void(*callback)(void*user_data), void* user_data); 

template<typename AnyClass, (AnyClass::*Func_Value)(void)> 
void wrap_method_callback(void* class_pointer) 
{ 
    AnyClass*const self = reinterpret_cast<AnyClass*>(class_pointer); 
    (self->*Func_Value)(); 
} 

class A 
{ 
public: 
    void callback() 
    { std::cout << m_i << std::endl; } 
    int m_i; 
}; 

int main() 
{ 
    Timer t; 
    A a = { 10 }; 
    t.register_callback(&wrap_method_callback<A,&A::callback>, &a); 
} 

Creo que una mejor solución sería actualizar call callback para usar boost :: function o una versión de fabricación propia (como la respuesta de Kornel). Sin embargo, esto requiere desarrolladores reales de C++ para involucrarse, de lo contrario, es muy probable que introduzca errores.

La ventaja de mi solución es que es solo una función de plantilla. No mucho puede salir mal. Una de las desventajas de mi solución es que puede cortar su clase con el molde a void* y viceversa. Tenga cuidado de que solo los punteros AnyClass* se pasen como void* al registro de devolución de llamada.

Cuestiones relacionadas