que - Devolución de llamada orientada a objetos para C++?
programacion orientada a objetos ejemplos resueltos (7)
¿Qué hay de functor
¿Hay alguna biblioteca que me permita crear de manera fácil y conveniente devoluciones de llamada orientadas a objetos en c ++?
el lenguaje Eiffel, por ejemplo, tiene el concepto de "agentes" que más o menos funcionan así:
class Foo{
public:
Bar* bar;
Foo(){
bar = new Bar();
bar->publisher.extend(agent say(?,"Hi from Foo!", ?));
bar->invokeCallback();
}
say(string strA, string strB, int number){
print(strA + " " + strB + " " + number.out);
}
}
class Bar{
public:
ActionSequence<string, int> publisher;
Bar(){}
invokeCallback(){
publisher.call("Hi from Bar!", 3);
}
}
la salida será: ¡Hola de Bar! 3 Hola de Foo!
Por lo tanto, el agente permite encapsular una función de miembro en un objeto, asignarle algunos parámetros de llamada predefinidos (Hola de Foo), especificar los parámetros de apertura (?) Y pasarlo a algún otro objeto que luego pueda invocarlo.
Como c ++ no permite crear indicadores de funciones en funciones miembro no estáticas, no parece tan trivial implementar algo tan fácil de usar en c ++. encontré algunos artículos con google en devoluciones de llamada orientadas a objetos en c ++, sin embargo, en realidad estoy buscando algunos archivos de biblioteca o encabezado que simplemente puedo importar, lo que me permite utilizar una sintaxis similarmente elegante.
Alguien tiene algunos consejos para mí?
¡Gracias!
C ++ permite indicadores de función en objetos miembros.
Mira aquí para más detalles.
También puede usar boost.signals o boost.signals2 (depanding si su programa es multiproceso o no).
Hay muchas posibilidades en C ++, el problema generalmente es uno de sintaxis.
- Puede usar puntero a funciones cuando no requiere estado, pero la sintaxis es realmente horrible. Esto se puede combinar con
boost::bind
para una sintaxis aún más ... interesante ... (*) - Corrijo su suposición falsa, de hecho es posible tener un puntero a una función miembro, la sintaxis es tan incómoda que se escapará (*)
- Puede usar objetos Functor, básicamente un Functor es un objeto que sobrecarga el operador
()
, por ejemplovoid Functor::operator()(int a) const;
, porque es un objeto que tiene estado y puede derivar de una interfaz común - Simplemente puede crear su propia jerarquía, con un nombre más agradable para la función de devolución de llamada si no desea ir al camino de sobrecarga del operador
- Finalmente, puede aprovechar las ventajas de C ++ 0x:
std::function
+ las funciones lambda son realmente increíbles cuando se trata de expresividad.
Agradecería una revisión sobre la sintaxis lambda;)
Foo foo;
std::function<void(std::string const&,int)> func =
[&foo](std::string const& s, int i) {
return foo.say(s,"Hi from Foo",i);
};
func("Hi from Bar", 2);
func("Hi from FooBar", 3);
Por supuesto, func
solo es viable mientras que foo
es viable (problema de alcance), puede copiar foo
usando [=foo]
para indicar pasar por valor en lugar de pasar por referencia.
Hay varias bibliotecas que te permiten hacer eso. Mira la función boost ::.
O prueba tu propia implementación simple:
template <typename ClassType, typename Result>
class Functor
{
typedef typename Result (ClassType::*FunctionType)();
ClassType* obj;
FunctionType fn;
public:
Functor(ClassType& object, FunctionType method): obj(&object), fn(method) {}
Result Invoke()
{
return (*obj.*fn)();
}
Result operator()()
{
return Invoke();
}
};
Uso:
class A
{
int value;
public:
A(int v): value(v) {}
int getValue() { return value; }
};
int main()
{
A a(2);
Functor<A, int> fn(a, &A::getValue);
cout << fn();
}
La forma más OO de utilizar Callbacks en C ++ es llamar a una función de una interfaz y luego pasar una implementación de esa interfaz.
#include <iostream>
class Interface
{
public:
virtual void callback() = 0;
};
class Impl : public Interface
{
public:
virtual void callback() { std::cout << "Hi from Impl/n"; }
};
class User
{
public:
User(Interface& newCallback) : myCallback(newCallback) { }
void DoSomething() { myCallback.callback(); }
private:
Interface& myCallback;
};
int main()
{
Impl cb;
User user(cb);
user.DoSomething();
}
Las personas generalmente usan uno de varios patrones:
Herencia. Es decir, define una clase abstracta que contiene la devolución de llamada. Luego tomas un puntero / referencia a él. Eso significa que cualquiera puede heredar y proporcionar esta devolución de llamada.
class Foo {
virtual void MyCallback(...) = 0;
virtual ~Foo();
};
class Base {
std::auto_ptr<Foo> ptr;
void something(...) {
ptr->MyCallback(...);
}
Base& SetCallback(Foo* newfoo) { ptr = newfoo; return *this; }
Foo* GetCallback() { return ptr; }
};
Herencia de nuevo Es decir, su clase de raíz es abstracta, y el usuario hereda de ella y define las devoluciones de llamada, en lugar de tener una clase concreta y objetos de devolución de llamada dedicados.
class Foo {
virtual void MyCallback(...) = 0;
...
};
class RealFoo : Foo {
virtual void MyCallback(...) { ... }
};
Aún más herencia, estática. De esta forma, puede usar plantillas para cambiar el comportamiento de un objeto. Es similar a la segunda opción, pero funciona en tiempo de compilación en lugar de en tiempo de ejecución, lo que puede generar diversos beneficios y desventajas, según el contexto.
template<typename T> class Foo {
void MyCallback(...) {
T::MyCallback(...);
}
};
class RealFoo : Foo<RealFoo> {
void MyCallback(...) {
...
}
};
Puede tomar y usar punteros de función de miembro o punteros de función regulares
class Foo {
void (*callback)(...);
void something(...) { callback(...); }
Foo& SetCallback( void(*newcallback)(...) ) { callback = newcallback; return *this; }
void (*)(...) GetCallback() { return callback; }
};
Hay objetos de función: sobrecargan el operador (). Querrá usar o escribir un contenedor funcional, actualmente provisto en la función std :: / boost ::, pero también mostraré uno simple aquí. Es similar al primer concepto, pero oculta la implementación y acepta una amplia gama de otras soluciones. Personalmente, normalmente uso esto como mi método de devolución de llamada de elección.
class Foo {
virtual ... Call(...) = 0;
virtual ~Foo();
};
class Base {
std::auto_ptr<Foo> callback;
template<typename T> Base& SetCallback(T t) {
struct NewFoo : Foo {
T t;
NewFoo(T newt) : t(newt) {}
... Call(...) { return t(...); }
};
callback = new NewFoo<T>(t);
return this;
}
Foo* GetCallback() { return callback; }
void dosomething() { callback->Call(...); }
};
La solución correcta depende principalmente del contexto. Si necesita exponer una API estilo C, entonces los punteros a función son la única manera de hacerlo (recuerde void * para los argumentos del usuario). Si necesita variar en el tiempo de ejecución (por ejemplo, exponer el código en una biblioteca precompilada), entonces la herencia estática no se puede usar aquí.
Solo una nota rápida: puse a mano ese código, por lo que no será perfecto (como modificadores de acceso para funciones, etc.) y es posible que tenga un par de errores. Es un ejemplo.
Uniéndose a la idea de functors - use std :: tr1 :: function y boost :: bind para construir los argumentos en él antes de registrarlo.