2010-08-01 14 views
11

Quiero implementar una clase en C++ que tenga una devolución de llamada.¿Cómo implemento una devolución de llamada en C++?

Así que creo que necesito un método que tiene 2 argumentos:

  • el objeto de destino. (digamos * myObj)
  • el puntero a una función miembro de el objeto de destino. (Por lo que puedo hacer * myObj-> memberFunc();)

Las condiciones son:

  • myObj puede ser de cualquier clase.

  • la función de miembro que va a ser la función de devolución de llamada no es estática.

He estado leyendo sobre esto pero parece que necesito saber la clase de myObj antes de la mano. Pero no estoy seguro de cómo hacerlo. ¿Cómo puedo manejar esto? ¿Es esto posible en C++?

Esto es algo que tengo en mente, pero seguramente es incorrecto.

class MyClassWithCallback{ 
public 
    void *targetObj; 
    void (*callback)(int number); 
    void setCallback(void *myObj, void(*callbackPtr)(int number)){ 
     targetObj = myObj; 
     callback = callbackPtr; 
    }; 
    void callCallback(int a){ 
     (myObj)->ptr(a); 
    }; 
}; 
class Target{ 
public 
    int res; 
    void doSomething(int a){//so something here. This is gonna be the callback function};   
}; 

int main(){ 
    Target myTarget; 
    MyClassWithCallback myCaller; 
    myCaller.setCallback((void *)&myTarget, &doSomething); 

}

Agradezco cualquier ayuda.

Gracias.

ACTUALIZACIÓN La mayoría de ustedes dijo Observando y Delegación, bueno, eso es exactamente lo que estoy buscando, soy un chico con mentalidad de Objective-C/Cocoa. Mi implementación actual usa interfaces con funciones virtuales. Es solo que pensé que sería "más inteligente" simplemente pasar el objeto y un puntero de función miembro (¡como aumentar!) En lugar de definir una interfaz. Pero parece que todo el mundo está de acuerdo en que las interfaces son la forma más fácil, ¿verdad? Boost parece ser una buena idea, (asumiendo que está instalado)

+0

patrón Observer requiere cambios en la interfaz de la clase "observados", a veces es un uso mucho más sencillo y claro '' function' y bind' en lugar de interfaces. También tenga en cuenta que si usa gcc reciente, ya tiene 'function' y' bind', por lo que no necesita boost. Usando la función de vinculación/es patrones de programación funcionales, mientras que el uso de interfaces está más con patrón de diseño "estándar" que tiene diferente significado – Artyom

Respuesta

16

La mejor solución, utilice boost::function con boost::bind , o si su compilador es compatible con tr1/C++ 0x, use std::tr1::function y std::tr1::bind.

Por lo tanto, es tan simple como:

boost::function<void()> callback; 
Target myTarget; 
callback=boost::bind(&Target::doSomething,&myTarget); 

callback(); // calls the function 

Y su devolución de llamada conjunto se convierte en:

class MyClassWithCallback{ 
public: 
    void setCallback(boost::function<void()> const &cb) 
    { 
    callback_ = cb; 
    } 
    void call_it() { callback_(); } 
private: 
    boost::function<void()> callback_; 
}; 

De lo contrario es necesario implementar alguna clase abstracta

struct callback { 
virtual void call() = 0; 
virtual ~callback() {} 
}; 

struct TargetCallback { 
virtual void call() { ((*self).*member)()); } 
void (Target::*member)(); 
Target *self; 
TargetCallback(void (Target::*m)(),Target *p) : 
     member(m), 
     self(p) 
{} 
}; 

y luego usar:

myCaller.setCallback(new TargetCallback(&Target::doSomething,&myTarget)); 

Cuando la clase se modifican en:

class MyClassWithCallback{ 
public: 
    void setCallback(callback *cb) 
    { 
    callback_.reset(cb); 
    } 
    void call_it() { callback_->call(); } 
private: 
    std::auto_ptr<callback> callback_; 
}; 

Y por supuesto, si la función que desea llamar no cambia es posible que sólo poner en práctica alguna de las interfaces, es decir, se derivan de destino de alguna clase abstracta con esta llamada.

+0

1 para el uso de impulso en una manera agradable –

+0

1 , boost :: bind definitivamente es el camino a seguir para C++ - solo callback – Calvin1602

+0

boost: bind definitivamente es la respuesta que estaba buscando. Sin embargo, probablemente tenga que instalar impulso. – nacho4d

8

Un truco es usar interfaces en su lugar, de esa manera no necesita conocer específicamente la clase en su 'MyClassWithCallback', si el objeto pasó en implementa el interfaz.

p. Ej. (Pseudo código)

struct myinterface 
{ 
    void doSomething()=0; 
}; 

class Target : public myinterface { ..implement doSomething... }; 

y

myinterface *targetObj; 
void setCallback(myinterface *myObj){ 
    targetObj = myObj; 
}; 

hacer la devolución de llamada

targetObj->doSomething(); 

su puesta en marcha:

Target myTarget; 
MyClassWithCallback myCaller; 
myCaller.setCallback(myTarget); 
+1

http://en.wikipedia.org/wiki/Observer_pattern –

5

El patrón de diseño Observer parece ser lo que estás buscando.

2

En C++, apenas se utilizan punteros a los métodos de clase. El hecho de que haya llamado es delegado y no se recomienda su uso. En lugar de ellos, debe usar funciones virtuales y clases abstractas. Sin embargo, C++ no me hubiera querido tanto, si no hubiera sido compatible con conceptos de programación completamente diferentes. Si aún desea delegados, debe mirar hacia "boost functional" (parte de C + +0 x), permite punteros a los métodos de clases, independientemente del nombre de la clase. Además, en C++ Builder tiene tipo __closure - implementación de un delegado en el nivel del compilador.

P.S. Lo siento por mala Inglés ...

4

Usted tiene algunas opciones básicas:

1) determinará qué clase de la devolución de llamada se va a usar, por lo que se conocen los tipos de puntero puntero del objeto y funciones miembro, y puede ser utilizado en la persona que llama. La clase puede tener varias funciones miembro con la misma firma, entre las que puede elegir, pero sus opciones son bastante limitadas.

Una cosa que ha hecho mal en su código es que los punteros de función de miembro y los punteros de función libres en C++ no son los mismos, y no son tipos compatibles. Su función de registro de devolución de llamada toma un puntero de función, pero está tratando de pasarle un puntero de función miembro. No permitido. Además, el tipo de "este" objeto es parte del tipo de un puntero de función miembro, por lo que no existe tal cosa en C++ como "un puntero a cualquier función miembro que toma un número entero y devuelve vacío". Tiene que ser "un puntero a cualquier función de miembro del Objetivo que toma un número entero y devuelve vacío". De ahí las opciones limitadas.

2) Defina una función virtual pura en una clase de interfaz. Cualquier clase que desee recibir la devolución de llamada, por lo tanto, puede heredar de la clase de interfaz. Gracias a la herencia múltiple, esto no interfiere con el resto de la jerarquía de su clase. Esto es casi exactamente lo mismo que definir una interfaz en Java.

3) Utilice una función no miembro para la devolución de llamada. Para cada clase que quiera usarlo, escriba una pequeña función sin stub que toma el puntero del objeto y llama a la función del miembro derecho sobre él.Por lo que en su caso se tendría:

dosomething_stub(void *obj, int a) { 
    ((Target *)obj)->doSomething(a); 
} 

4) utilizar plantillas:

template<typename CB> class MyClassWithCallback { 
    CB *callback; 
public: 
    void setCallback(CB &cb) { callback = &cb; } 
    void callCallback(int a) { 
     callback(a); 
    } 
}; 

class Target { 
    void operator()(int a) { /* do something; */ } 
}; 

int main() { 
    Target t; 
    MyClassWithCallback<T> caller; 
    caller.setCallback(t); 
} 

sea que usted puede utilizar plantillas depende de si su ClassWithCallback es parte de un gran marco de edad - si es así, se podría no será posible (para ser precisos: podría requerir algunos trucos más, como una clase de plantilla que hereda de una clase que no sea de plantilla que tenga una función de miembro virtual), porque no puede necesariamente crear una instancia del marco completo una vez para cada destinatario de devolución de llamada.

Cuestiones relacionadas