Funciones de callback en c ++

En c ++, ¿cuándo y cómo se usa una función de callback?

EDITAR:
Me gustaría ver un ejemplo simple para escribir una función de callback.

Nota: La mayoría de las respuestas cubren los punteros de función, que es una posibilidad para lograr la lógica de “callback” en C ++, pero a partir de hoy no es la más favorable, creo.

¿Qué son las devoluciones de llamada (?) Y por qué usarlas (!)

Una callback es invocable (ver más abajo) aceptada por una clase o función, utilizada para personalizar la lógica actual dependiendo de esa callback.

Una razón para usar devoluciones de llamada es escribir un código genérico que sea independiente de la lógica en la función llamada y pueda reutilizarse con diferentes devoluciones de llamada.

Muchas funciones de la biblioteca de algoritmos estándar usan devoluciones de llamada. Por ejemplo, el algoritmo for_each aplica una callback unaria a cada elemento en un rango de iteradores:

 template UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f) { for (; first != last; ++first) { f(*first); } return f; } 

que se puede usar para incrementar primero y luego imprimir un vector pasando callables apropiados, por ejemplo:

 std::vector v{ 1.0, 2.2, 4.0, 5.5, 7.2 }; double r = 4.0; std::for_each(v.begin(), v.end(), [&](double & v) { v += r; }); std::for_each(v.begin(), v.end(), [](double v) { std::cout << v << " "; }); 

que imprime

 5 6.2 8 9.5 11.2 

Otra aplicación de las devoluciones de llamada es la notificación de llamadas de ciertos eventos que permite una cierta cantidad de flexibilidad de tiempo estático / de comstackción.

Personalmente, utilizo una biblioteca de optimización local que usa dos devoluciones de llamada diferentes:

  • La primera callback se invoca si se requiere un valor de función y el gradiente basado en un vector de valores de entrada (callback lógica: determinación de valor de función / derivación de gradiente).
  • La segunda callback se llama una vez para cada paso de algoritmo y recibe cierta información sobre la convergencia del algoritmo (callback de notificación).

Por lo tanto, el diseñador de la biblioteca no se encarga de decidir qué sucede con la información que se le proporciona al progtwigdor a través de la devolución de la notificación y no tiene que preocuparse por cómo determinar los valores de la función porque la callback lógica los proporciona. Hacer las cosas bien es una tarea debida al usuario de la biblioteca y mantiene la biblioteca delgada y más genérica.

Además, las devoluciones de llamada pueden habilitar el comportamiento de tiempo de ejecución dynamic.

Imagine un tipo de clase de motor de juego que tiene una función que se activa, cada vez que los usuarios presiona un botón en su teclado y un conjunto de funciones que controlan el comportamiento de su juego. Con devoluciones de llamada, puede (re) decidir en el tiempo de ejecución qué acción se tomará.

 void player_jump(); void player_crouch(); class game_core { std::array actions; // void key_pressed(unsigned key_id) { if(actions[key_id]) actions[key_id](); } // update keybind from menu void update_keybind(unsigned key_id, void(*new_action)()) { actions[key_id] = new_action; } }; 

Aquí la función key_pressed utiliza las devoluciones de llamada almacenadas en actions para obtener el comportamiento deseado cuando se presiona una tecla determinada. Si el jugador elige cambiar el botón para saltar, el motor puede llamar

 game_core_instance.update_keybind(newly_selected_key, &player_jump); 

y así cambiar el comportamiento de una llamada a key_pressed (que las llamadas player_jump ) una vez que se presiona este botón la próxima vez en el juego.

¿Qué son los callables en C ++ (11)?

Ver conceptos de C ++: invocable en cppreference para una descripción más formal.

La funcionalidad de callback se puede realizar de varias maneras en C ++ (11) ya que varias cosas diferentes resultan ser invocables * :

  • Punteros de función (incluidos punteros a funciones de miembro)
  • std::function objetos de std::function
  • Expresiones Lambda
  • Expresiones de enlace
  • Objetos de función (clases con operador de llamada de función sobrecargada operator() )

* Nota: El puntero a los miembros de datos también se puede llamar, pero no se llama ninguna función en absoluto.

Varias formas importantes de escribir devoluciones de llamadas en detalle

  • X.1 "Escribir" una callback en esta publicación significa la syntax para declarar y nombrar el tipo de callback.
  • X.2 "Llamar" a una callback se refiere a la syntax para llamar a esos objetos.
  • X.3 "Usar" una callback significa la syntax al pasar argumentos a una función usando una callback.

Nota: A partir de C ++ 17, una llamada como f(...) se puede escribir como std::invoke(f, ...) que también maneja el puntero al caso del miembro.

1. Punteros de función

Un puntero de función es el tipo más simple (en términos de generalidad, en términos de legibilidad, posiblemente el peor) que puede tener una callback.

Tengamos una función simple foo :

 int foo (int x) { return 2+x; } 

1.1 Escribir una función de puntero / tipo de notación

Un tipo de puntero a función tiene la notación

 return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3) // ie a pointer to foo has the type: int (*)(int) 

donde se verá un tipo de puntero a función nombrada

 return_type (* name) (parameter_type_1, parameter_type_2, parameter_type_3) // ie f_int_t is a type: function pointer taking one int argument, returning int typedef int (*f_int_t) (int); // foo_p is a pointer to function taking int returning int // initialized by pointer to function foo taking int returning int int (* foo_p)(int) = &foo; // can alternatively be written as f_int_t foo_p = &foo; 

La statement using nos da la opción de hacer las cosas un poco más legibles, ya que typedef para f_int_t también se puede escribir como:

 using f_int_t = int(*)(int); 

Donde (al menos para mí) es más claro que f_int_t es el nuevo tipo de alias y el reconocimiento del tipo de puntero a la función también es más fácil

Y una statement de una función utilizando una callback del tipo de puntero a función será:

 // foobar having a callback argument named moo of type // pointer to function returning int taking int as its argument int foobar (int x, int (*moo)(int)); // if f_int is the function pointer typedef from above we can also write foobar as: int foobar (int x, f_int_t moo); 

1.2 Notación de llamada de callback

La notación de llamada sigue la syntax de llamada de función simple:

 int foobar (int x, int (*moo)(int)) { return x + moo(x); // function pointer moo called using argument x } // analog int foobar (int x, f_int_t moo) { return x + moo(x); // function pointer moo called using argument x } 

1.3 Notación de uso de callback y tipos compatibles

Una función de callback que toma un puntero de función se puede llamar usando punteros de función.

Usar una función que toma una callback del puntero de función es bastante simple:

  int a = 5; int b = foobar(a, foo); // call foobar with pointer to foo as callback // can also be int b = foobar(a, &foo); // call foobar with pointer to foo as callback 

1.4 Ejemplo

Se puede escribir una función que no dependa de cómo funciona la callback:

 void tranform_every_int(int * v, unsigned n, int (*fp)(int)) { for (unsigned i = 0; i < n; ++i) { v[i] = fp(v[i]); } } 

donde sea posible, las devoluciones de llamada podrían ser

 int double_int(int x) { return 2*x; } int square_int(int x) { return x*x; } 

usado como

 int a[5] = {1, 2, 3, 4, 5}; tranform_every_int(&a[0], 5, double_int); // now a == {2, 4, 6, 8, 10}; tranform_every_int(&a[0], 5, square_int); // now a == {4, 16, 36, 64, 100}; 

2. Puntero a la función miembro

Un puntero a la función miembro (de alguna clase C ) es un tipo especial de puntero de función (y aún más complejo) que requiere un objeto de tipo C para operar.

 struct C { int y; int foo(int x) const { return x+y; } }; 

2.1 Escribiendo puntero a la función miembro / notación de tipo

Un puntero al tipo de función miembro para alguna clase T tiene la notación

 // can have more or less parameters return_type (T::*)(parameter_type_1, parameter_type_2, parameter_type_3) // ie a pointer to C::foo has the type int (C::*) (int) 

donde un puntero con nombre a la función miembro , en analogía con el puntero a la función, tendrá el siguiente aspecto:

 return_type (T::* name) (parameter_type_1, parameter_type_2, parameter_type_3) // ie a type `f_C_int` representing a pointer to member function of `C` // taking int returning int is: typedef int (C::* f_C_int_t) (int x); // The type of C_foo_p is a pointer to member function of C taking int returning int // Its value is initialized by a pointer to foo of C int (C::* C_foo_p)(int) = &C::foo; // which can also be written using the typedef: f_C_int_t C_foo_p = &C::foo; 

Ejemplo: declarar una función tomando un puntero a la callback de la función miembro como uno de sus argumentos:

 // C_foobar having an argument named moo of type pointer to member function of C // where the callback returns int taking int as its argument // also needs an object of type c int C_foobar (int x, C const &c, int (C::*moo)(int)); // can equivalently declared using the typedef above: int C_foobar (int x, C const &c, f_C_int_t moo); 

2.2 Notación de llamada de callback

Se puede invocar el puntero a la función miembro de C , con respecto a un objeto de tipo C mediante el uso de operaciones de acceso de miembro en el puntero desreferenciado. Nota: ¡Se requiere paréntesis!

 int C_foobar (int x, C const &c, int (C::*moo)(int)) { return x + (c.*moo)(x); // function pointer moo called for object c using argument x } // analog int C_foobar (int x, C const &c, f_C_int_t moo) { return x + (c.*moo)(x); // function pointer moo called for object c using argument x } 

Nota: Si hay un puntero a C disponible, la syntax es equivalente (donde también se debe desreferenciar el puntero a C ):

 int C_foobar_2 (int x, C const * c, int (C::*meow)(int)) { if (!c) return x; // function pointer meow called for object *c using argument x return x + ((*c).*meow)(x); } // or equivalent: int C_foobar_2 (int x, C const * c, int (C::*meow)(int)) { if (!c) return x; // function pointer meow called for object *c using argument x return x + (c->*meow)(x); } 

2.3 Notación de uso de callback y tipos compatibles

Una función de callback que toma un puntero de función miembro de la clase T se puede llamar usando un puntero de función miembro de clase T

El uso de una función que toma un puntero a la callback de la función miembro es, por analogía a los punteros de función, bastante simple también:

  C my_c{2}; // aggregate initialization int a = 5; int b = C_foobar(a, my_c, &C::foo); // call C_foobar with pointer to foo as its callback 

3. std::function objetos de std::function (encabezado )

La clase std::function es una envoltura de función polimórfica para almacenar, copiar o invocar callables.

3.1 Escribir una notación std::function object / type

El tipo de un objeto std::function que almacena un llamante se ve así:

 std::function // ie using the above function declaration of foo: std::function stdf_foo = &foo; // or C::foo: std::function stdf_C_foo = &C::foo; 

3.2 Notación de llamada de callback

La función class std::function tiene operator() definido que se puede usar para invocar su objective.

 int stdf_foobar (int x, std::function moo) { return x + moo(x); // std::function moo called } // or int stdf_C_foobar (int x, C const &c, std::function moo) { return x + moo(c, x); // std::function moo called using c and x } 

3.3 Notación de uso de callback y tipos compatibles

La callback std::function es más genérica que los punteros de función o puntero a la función de miembro, ya que se pueden pasar diferentes tipos e implícitamente se pueden convertir en un objeto std::function .

3.3.1 Indicadores de función y punteros a funciones miembro

Un puntero a la función

 int a = 2; int b = stdf_foobar(a, &foo); // b == 6 ( 2 + (2+2) ) 

o un puntero a la función miembro

 int a = 2; C my_c{7}; // aggregate initialization int b = stdf_C_foobar(a, c, &C::foo); // b == 11 == ( 2 + (7+2) ) 

puede ser usado.

3.3.2 Expresiones Lambda

Un cierre sin nombre de una expresión lambda se puede almacenar en un objeto std::function :

 int a = 2; int c = 3; int b = stdf_foobar(a, [c](int x) -> int { return 7+c*x; }); // b == 15 == a + (7*c*a) == 2 + (7+3*2) 

3.3.3 std::bind expresiones de std::bind

El resultado de una expresión std::bind se puede pasar. Por ejemplo, al vincular parámetros a una llamada de puntero de función:

 int foo_2 (int x, int y) { return 9*x + y; } using std::placeholders::_1; int a = 2; int b = stdf_foobar(a, std::bind(foo_2, _1, 3)); // b == 23 == 2 + ( 9*2 + 3 ) int c = stdf_foobar(a, std::bind(foo_2, 5, _1)); // c == 49 == 2 + ( 9*5 + 2 ) 

Donde también se pueden enlazar objetos como el objeto para la invocación de puntero a funciones miembro:

 int a = 2; C const my_c{7}; // aggregate initialization int b = stdf_foobar(a, std::bind(&C::foo, my_c, _1)); // b == 1 == 2 + ( 2 + 7 ) 

3.3.4 Objetos de función

Los objetos de clases que tienen una sobrecarga de operator() adecuada también se pueden almacenar dentro de un objeto std::function .

 struct Meow { int y = 0; Meow(int y_) : y(y_) {} int operator()(int x) { return y * x; } }; int a = 11; int b = stdf_foobar(a, Meow{8}); // b == 99 == 11 + ( 8 * 11 ) 

3.4 Ejemplo

Cambiar el ejemplo del puntero a la función para usar std::function

 void stdf_tranform_every_int(int * v, unsigned n, std::function fp) { for (unsigned i = 0; i < n; ++i) { v[i] = fp(v[i]); } } 

le da mucha más utilidad a esa función porque (ver 3.3) tenemos más posibilidades de usarla:

 // using function pointer still possible int a[5] = {1, 2, 3, 4, 5}; stdf_tranform_every_int(&a[0], 5, double_int); // now a == {2, 4, 6, 8, 10}; // use it without having to write another function by using a lambda stdf_tranform_every_int(&a[0], 5, [](int x) -> int { return x/2; }); // now a == {1, 2, 3, 4, 5}; again // use std::bind : int nine_x_and_y (int x, int y) { return 9*x + y; } using std::placeholders::_1; // calls nine_x_and_y for every int in a with y being 4 every time stdf_tranform_every_int(&a[0], 5, std::bind(nine_x_and_y, _1, 4)); // now a == {13, 22, 31, 40, 49}; 

4. Tipo de callback con plantilla

Usando plantillas, el código que llama a la callback puede ser incluso más general que usar objetos std::function .

Tenga en cuenta que las plantillas son una característica de tiempo de comstackción y son una herramienta de diseño para el polymorphism en tiempo de comstackción. Si el comportamiento dynamic del tiempo de ejecución se logra a través de devoluciones de llamada, las plantillas ayudarán pero no inducirán la dinámica del tiempo de ejecución.

4.1 Escribir (escribir notaciones) y llamar callbacks con plantilla

Generalizando, es decir, el código std_ftransform_every_int desde arriba puede lograrse aún más mediante el uso de plantillas:

 template void stdf_transform_every_int_templ(int * v, unsigned const n, std::function fp) { for (unsigned i = 0; i < n; ++i) { v[i] = fp(v[i]); } } 

con una syntax aún más general (y más fácil) para un tipo de callback que es un argumento sencillo, por deducir, de plantilla:

 template void transform_every_int_templ(int * v, unsigned const n, F f) { std::cout << "transform_every_int_templ<" << type_name() << ">\n"; for (unsigned i = 0; i < n; ++i) { v[i] = f(v[i]); } } 

Nota: El resultado incluido imprime el nombre de tipo deducido para el tipo de plantilla F La implementación de type_name se da al final de esta publicación.

La implementación más general para la transformación unaria de un rango es parte de la biblioteca estándar, concretamente std::transform , que también está modelada con respecto a los tipos iterados.

 template OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op) { while (first1 != last1) { *d_first++ = unary_op(*first1++); } return d_first; } 

4.2 Ejemplos utilizando callbacks con plantilla y tipos compatibles

Los tipos compatibles para el método de callback con plantilla std::function stdf_transform_every_int_templ son idénticos a los tipos mencionados anteriormente (consulte 3.4).

Sin embargo, al usar la versión con plantilla, la firma de la callback utilizada puede cambiar un poco:

 // Let int foo (int x) { return 2+x; } int muh (int const &x) { return 3+x; } int & woof (int &x) { x *= 4; return x; } int a[5] = {1, 2, 3, 4, 5}; stdf_transform_every_int_templ(&a[0], 5, &foo); // a == {3, 4, 5, 6, 7} stdf_transform_every_int_templ(&a[0], 5, &muh); // a == {6, 7, 8, 9, 10} stdf_transform_every_int_templ(&a[0], 5, &woof); 

Nota: std_ftransform_every_int (versión no std_ftransform_every_int ; ver arriba) funciona con foo pero no usa muh .

 // Let void print_int(int * p, unsigned const n) { bool f{ true }; for (unsigned i = 0; i < n; ++i) { std::cout << (f ? "" : " ") << p[i]; f = false; } std::cout << "\n"; } 

El parámetro de plantilla simple de transform_every_int_templ puede ser de todos los tipos posibles.

 int a[5] = { 1, 2, 3, 4, 5 }; print_int(a, 5); transform_every_int_templ(&a[0], 5, foo); print_int(a, 5); transform_every_int_templ(&a[0], 5, muh); print_int(a, 5); transform_every_int_templ(&a[0], 5, woof); print_int(a, 5); transform_every_int_templ(&a[0], 5, [](int x) -> int { return x + x + x; }); print_int(a, 5); transform_every_int_templ(&a[0], 5, Meow{ 4 }); print_int(a, 5); using std::placeholders::_1; transform_every_int_templ(&a[0], 5, std::bind(foo_2, _1, 3)); print_int(a, 5); transform_every_int_templ(&a[0], 5, std::function{&foo}); print_int(a, 5); 

El código anterior se imprime:

 1 2 3 4 5 transform_every_int_templ  3 4 5 6 7 transform_every_int_templ  6 8 10 12 14 transform_every_int_templ  9 11 13 15 17 transform_every_int_templ  27 33 39 45 51 transform_every_int_templ  108 132 156 180 204 transform_every_int_templ , int))(int, int)>> 975 1191 1407 1623 1839 transform_every_int_templ > 977 1193 1409 1625 1841 

type_name implementación de type_name utilizada anteriormente

 #include  #include  #include  #include  #include  template  std::string type_name() { typedef typename std::remove_reference::type TR; std::unique_ptr own (abi::__cxa_demangle(typeid(TR).name(), nullptr, nullptr, nullptr), std::free); std::string r = own != nullptr?own.get():typeid(TR).name(); if (std::is_const::value) r += " const"; if (std::is_volatile::value) r += " volatile"; if (std::is_lvalue_reference::value) r += " &"; else if (std::is_rvalue_reference::value) r += " &&"; return r; } 

También existe la forma C de hacer devoluciones de llamada: indicadores de función

 //Define a type for the callback signature, //it is not necessary, but makes life easier //Function pointer called CallbackType that takes a float //and returns an int typedef int (*CallbackType)(float); void DoWork(CallbackType callback) { float variable = 0.0f; //Do calculations //Call the callback with the variable, and retrieve the //result int result = callback(variable); //Do something with the result } int SomeCallback(float variable) { int result; //Interpret variable return result; } int main(int argc, char ** argv) { //Pass in SomeCallback to the DoWork DoWork(&SomeCallback); } 

Ahora, si desea pasar métodos de clase como devoluciones de llamada, las declaraciones a esos indicadores de función tienen declaraciones más complejas, por ejemplo:

 //Declaration: typedef int (ClassName::*CallbackType)(float); //This method performs work using an object instance void DoWorkObject(CallbackType callback) { //Class instance to invoke it through ClassName objectInstance; //Invocation int result = (objectInstance.*callback)(1.0f); } //This method performs work using an object pointer void DoWorkPointer(CallbackType callback) { //Class pointer to invoke it through ClassName * pointerInstance; //Invocation int result = (pointerInstance->*callback)(1.0f); } int main(int argc, char ** argv) { //Pass in SomeCallback to the DoWork DoWorkObject(&ClassName::Method); DoWorkPointer(&ClassName::Method); } 

Scott Meyers da un buen ejemplo:

 class GameCharacter; int defaultHealthCalc(const GameCharacter& gc); class GameCharacter { public: typedef std::function HealthCalcFunc; explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf) { } int healthValue() const { return healthFunc(*this); } private: HealthCalcFunc healthFunc; }; 

Creo que el ejemplo lo dice todo.

std::function<> es la forma “moderna” de escribir callbacks de C ++.

Una función de callback es un método que se pasa a una rutina y en algún momento se llama por la rutina a la que se pasa.

Esto es muy útil para hacer software reutilizable. Por ejemplo, muchas API del sistema operativo (como la API de Windows) utilizan de forma importante las devoluciones de llamada.

Por ejemplo, si desea trabajar con archivos en una carpeta, puede llamar a una función API, con su propia rutina, y su rutina se ejecutará una vez por archivo en la carpeta especificada. Esto permite que la API sea muy flexible.

La respuesta aceptada es muy útil y bastante completa. Sin embargo, los estados OP

Me gustaría ver un ejemplo simple para escribir una función de callback.

Así que aquí tienes, desde C ++ 11 tienes std::function por lo que no hay necesidad de punteros de función y cosas similares:

 #include  #include  #include  void print_hashes(std::function hash_calculator) { std::string strings_to_hash[] = {"you", "saved", "my", "day"}; for(auto s : strings_to_hash) std::cout << s << ":" << hash_calculator(s) << std::endl; } int main() { print_hashes( [](const std::string& str) { /** lambda expression */ int result = 0; for (int i = 0; i < str.length(); i++) result += pow(31, i) * str.at(i); return result; }); return 0; } 

Por cierto, este ejemplo es real, porque desea llamar a función print_hashes con diferentes implementaciones de funciones hash, para este propósito proporcioné una simple. Recibe una cadena, devuelve un int (un valor hash de la cadena proporcionada), y todo lo que necesita recordar de la parte de syntax es std::function que describe dicha función como una argumento de entrada de la función que lo invocará.

No hay un concepto explícito de una función de callback en C ++. Los mecanismos de callback a menudo se implementan a través de punteros de función, objetos de funcionador u objetos de callback. Los progtwigdores tienen que diseñar e implementar explícitamente la funcionalidad de callback.

Edición basada en comentarios:

A pesar de los comentarios negativos que ha recibido esta respuesta, no está mal. Trataré de hacer un mejor trabajo al explicar de dónde vengo.

C y C ++ tienen todo lo que necesita para implementar funciones de callback. La forma más común y trivial de implementar una función de callback es pasar un puntero a la función como un argumento de función.

Sin embargo, las funciones de callback y los indicadores de función no son sinónimos. Un puntero de función es un mecanismo de lenguaje, mientras que una función de callback es un concepto semántico. Los punteros de función no son la única forma de implementar una función de callback; también puede usar funtores e incluso funciones virtuales de variedad de jardín. Lo que hace que una función llame a una callback no es el mecanismo utilizado para identificar y llamar a la función, sino el contexto y la semántica de la llamada. Decir que algo es una función de callback implica una separación mayor que la normal entre la función llamante y la función específica que se llama, un acoplamiento conceptual más flexible entre el llamador y el destinatario, con el llamador teniendo control explícito sobre lo que se llama. Es esa noción confusa de un acoplamiento conceptual más flexible y una selección de funciones impulsada por el que llama que hace que algo sea una función de callback, no el uso de un puntero de función.

Por ejemplo, la documentación de .NET para IFormatProvider dice que “GetFormat es un método de callback” , a pesar de que es solo un método de interfaz de rutina. No creo que nadie discuta que todas las llamadas a métodos virtuales sean funciones de callback. Lo que hace que GetFormat sea un método de callback no es la mecánica de cómo se pasa o se invoca, sino la semántica de la selección del llamante a la que se llamará el método GetFormat de ese objeto.

Algunos lenguajes incluyen funciones con semántica de callback explícita, generalmente relacionadas con eventos y manejo de eventos. Por ejemplo, C # tiene el tipo de evento con syntax y semántica explícitamente diseñadas en torno al concepto de callback. Visual Basic tiene su cláusula Handles , que declara explícitamente que un método es una función de callback mientras abstrae el concepto de delegates o indicadores de función. En estos casos, el concepto semántico de una callback se integra en el lenguaje mismo.

C y C ++, por otro lado, no integran el concepto semántico de las funciones de callback de manera tan explícita. Los mecanismos están ahí, la semántica integrada no. Puede implementar funciones de callback sin problemas, pero para obtener algo más sofisticado que incluya una semántica de callback explícita, debe comstackrlo sobre lo que ofrece C ++, como lo que Qt hizo con sus Señales y Ranuras .

En pocas palabras, C ++ tiene lo que necesita para implementar devoluciones de llamadas, a menudo de manera bastante fácil y trivial utilizando punteros a funciones. Lo que no tiene son palabras clave y características cuya semántica es específica para las devoluciones de llamadas, como raise , emit , Handles , event + = , etc. Si proviene de un idioma con ese tipo de elementos, el soporte de callback nativo en C ++ se sentirá castrado

Las funciones de callback son parte del estándar C y, por lo tanto, también forman parte de C ++. Pero si está trabajando con C ++, le sugiero que use el patrón de observador en su lugar: http://en.wikipedia.org/wiki/Observer_pattern

Consulte la definición anterior donde indica que una función de callback pasa a otra función y en algún momento se llama.

En C ++, es deseable que las funciones de callback llamen a un método de clases. Cuando haces esto, tienes acceso a los datos de los miembros. Si usa la forma C de definir una callback, deberá apuntarla a una función miembro estática. Esto no es muy deseable.

Aquí es cómo puede usar devoluciones de llamada en C ++. Asume 4 archivos. Un par de archivos .CPP / .H para cada clase. Clase C1 es la clase con un método que queremos devolver. C2 vuelve a llamar al método de C1. En este ejemplo, la función de callback toma 1 parámetro que agregué para el bien de los lectores. El ejemplo no muestra ningún objeto que se crea una instancia y se usa. Un caso de uso para esta implementación es cuando tiene una clase que lee y almacena datos en un espacio temporal y otra que procesa los datos. Con una función de callback, por cada fila de datos leídos, la callback puede procesarla. Esta técnica corta la sobrecarga del espacio temporal requerido. Es particularmente útil para las consultas SQL que devuelven una gran cantidad de datos que luego tienen que ser procesados ​​posteriormente.

 ///////////////////////////////////////////////////////////////////// // C1 H file class C1 { public: C1() {}; ~C1() {}; void CALLBACK F1(int i); }; ///////////////////////////////////////////////////////////////////// // C1 CPP file void CALLBACK C1::F1(int i) { // Do stuff with C1, its methods and data, and even do stuff with the passed in parameter } ///////////////////////////////////////////////////////////////////// // C2 H File class C1; // Forward declaration class C2 { typedef void (CALLBACK C1::* pfnCallBack)(int i); public: C2() {}; ~C2() {}; void Fn(C1 * pThat,pfnCallBack pFn); }; ///////////////////////////////////////////////////////////////////// // C2 CPP File void C2::Fn(C1 * pThat,pfnCallBack pFn) { // Call a non-static method in C1 int i = 1; (pThat->*pFn)(i); } 

La función singals2 de Boost le permite suscribir funciones de miembros genéricas (¡sin plantillas!) Y de forma segura para hilos.

Ejemplo: Document-View Signals se puede usar para implementar architectures flexibles de Document-View. El documento contendrá una señal a la que se puede conectar cada una de las vistas. La siguiente clase de documento define un documento de texto simple que admite múltiples vistas. Tenga en cuenta que almacena una sola señal a la que se conectarán todas las vistas.

 class Document { public: typedef boost::signals2::signal signal_t; public: Document() {} /* Connect a slot to the signal which will be emitted whenever text is appended to the document. */ boost::signals2::connection connect(const signal_t::slot_type &subscriber) { return m_sig.connect(subscriber); } void append(const char* s) { m_text += s; m_sig(); } const std::string& getText() const { return m_text; } private: signal_t m_sig; std::string m_text; }; 

Next, we can begin to define views. The following TextView class provides a simple view of the document text.

 class TextView { public: TextView(Document& doc): m_document(doc) { m_connection = m_document.connect(boost::bind(&TextView::refresh, this)); } ~TextView() { m_connection.disconnect(); } void refresh() const { std::cout << "TextView: " << m_document.getText() << std::endl; } private: Document& m_document; boost::signals2::connection m_connection; };