2010-08-19 14 views
8

¿Hay alguna biblioteca que me permita crear de manera fácil y conveniente devoluciones de llamada orientadas a objetos en C++?Devolución de llamada orientada a objetos para C++?

el lenguaje Eiffel, por ejemplo, tiene el concepto de "agentes" que más o menos trabajo como esto: será

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); 
    } 
} 

de salida: Hola de Bar! 3 Hola de Foo!

Entonces, el agente permite encapsular una función miembro en un objeto, asignarla a algunos parámetros de llamada predefinidos (Hola desde Foo), especificar los parámetros abiertos (?) Y pasarla a algún otro objeto que pueda invocar eso mas tarde.

Dado que C++ no permite crear indicadores de función 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 usar una sintaxis similar elegante.

¿Alguien tiene algunos consejos para mí?

Gracias!

+0

No estoy seguro de entender por completo tu sintaxis, pero 'boost :: bind' se puede usar para empaquetar funciones y funciones miembro con una interfaz adecuada en un objeto. http://www.boost.org/doc/libs/1_44_0/libs/bind/bind.html#with_member_pointers –

+0

¡hey! sí, creo que eso es lo que estoy buscando. Desafortunadamente no puedo compilar impulso para el iPhone (desarrollo en el iPhone). He leído que esto también es posible de hacer con stl. ¿alguien puede explicar cómo? – Mat

Respuesta

2

C++ permite indicadores de función en objetos miembros.
Ver here para más detalles.
También puede usar boost.signals o boost.signals2 (depanding si su programa es multiproceso o no).

+0

sí, pero solo funciones de miembro estático. pero quiero que se notifique al objeto en sí – Mat

+0

No, cualquier función miembro puede ser un puntero a una función miembro. –

+0

@Mat Insisto porque Mat parece no captarlo: C++ PERMITIR indicadores de funciones no estáticas de miembros. Sin embargo, la forma más inteligente de hacer lo que desea es un functor (de hecho, simplemente se trata de definir el operador de paréntesis para que sus objetos se puedan comportar como funciones) como MeThinks ha respondido. –

10

La forma más OO utilizar devoluciones de llamada en C++ es llamar a una función de una interfaz y luego pasar una implementación de la 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(); 
} 
1

Hay varias bibliotecas que le permiten hacer eso. Mira la función boost ::.

O probar su propia implementación sencilla:

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(); 
} 
1

Junto a la idea de funtores - utilizar std :: TR1 :: función y boost :: bind para construir los argumentos en él antes de registrarlo.

0

Hay muchas posibilidades en C++, generalmente el problema es la sintaxis.

  • Puede usar el puntero a las funciones cuando no necesita estado, pero la sintaxis es realmente horrible. Esto se puede combinar con boost::bind para un aún más ... interesante ...la sintaxis (*)
  • en lo correcto de su falso supuesto, es de hecho factible tener puntero a una función miembro, la sintaxis es tan incómoda que se encontrará lejos (*)
  • Puede utilizar objetos Functor, básicamente, una Functor es un objeto que sobrecarga el operador (), por ejemplo void Functor::operator()(int a) const;, porque es un objeto que tiene estado y puede derivarse de una interfaz común
  • Puede simplemente crear su propia jerarquía, con un nombre más agradable para la función de devolución de llamada si no 'quiero ir al operador de carreteras sobrecarga
  • Por último, usted puede tomar ventaja de C++ 0x instalaciones: std::function + las funciones lambda son verdaderamente impresionante cuando se trata de a la expresividad.

Le 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 sólo es viable mientras foo es viable (tema ámbito de aplicación), se puede copiar foo usando [=foo] para indicar paso por valor en lugar de pasar por referencia.

(*) Mandatory Tutorial on Function Pointers

5

Las personas suelen utilizar 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; } 
}; 

Inheritance again. 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 inheritance- static. 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 utilizar los punteros de función miembros o punteros a funciones regulares

class Foo { 
    void (*callback)(...); 
    void something(...) { callback(...); } 
    Foo& SetCallback(void(*newcallback)(...)) { callback = newcallback; return *this; } 
    void (*)(...) GetCallback() { return callback; } 
}; 

Hay función objetos- sobrecargan 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 tiempo de ejecución (por ejemplo, exponer código en una biblioteca precompilada), entonces la herencia estática no se puede usar aquí.

Solo una nota rápida: he actualizado ese código a mano, por lo que no será perfecto (como los modificadores de acceso para funciones, etc.) y puede tener un par de errores. Es un ejemplo.

Cuestiones relacionadas