c++ callback function-pointers

Funciones de devolución de llamada en c++



callback function-pointers (9)

En c ++, ¿cuándo y cómo utiliza una función de devolución de llamada?

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


La respuesta aceptada es muy útil y bastante completa. Sin embargo, el OP declara

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

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

#include <functional> #include <string> #include <iostream> void print_hashes(std::function<int (const std::string&)> 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; }

Este ejemplo es, por cierto, real, porque desea llamar a la función print_hashes con diferentes implementaciones de funciones hash, para este fin proporcioné una sencilla. Recibe una cadena, devuelve un int (un valor hash de la cadena provista), y todo lo que necesita recordar de la parte de sintaxis es std::function<int (const std::string&)> que describe dicha función como una Ingrese el argumento de la función que lo invocará.


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


No hay un concepto explícito de una función de devolución de llamada en C ++. Los mecanismos de devolución de llamada a menudo se implementan mediante punteros de función, objetos de functor u objetos de devolución de llamada. Los programadores tienen que diseñar e implementar explícitamente la funcionalidad de devolución de llamada.

Edición basada en comentarios:

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

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

Sin embargo, las funciones de devolución de llamada y los punteros de función no son sinónimos. Un puntero de función es un mecanismo de lenguaje, mientras que una función de devolución de llamada es un concepto semántico. Los punteros de función no son la única forma de implementar una función de devolución de llamada, también puede utilizar los funtores e incluso las funciones virtuales de variedad de jardín. Lo que hace que una función sea una devolución de llamada 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 devolución de llamada implica una separación mayor de lo normal entre la función de llamada y la función específica a la que se llama, un acoplamiento conceptual más flexible entre la persona que llama y la persona que llama, con la persona que llama que tiene control explícito sobre lo que recibe la llamada. Es esa noción difusa de acoplamiento conceptual más suelto y selección de función dirigida por el llamador lo que hace que algo sea una función de devolución de llamada, 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 devolución de llamada" , aunque solo es un método de interfaz run-of-the-mill. No creo que nadie pueda argumentar que todas las llamadas de métodos virtuales son funciones de devolución de llamada. Lo que hace que GetFormat sea un método de devolución de llamada no es la mecánica de cómo se pasa o se invoca, sino la semántica de la persona que llama, a la que se llamará el método GetFormat del objeto.

Algunos lenguajes incluyen características con semántica de devolución de llamada explícita, típicamente relacionadas con eventos y manejo de eventos. Por ejemplo, C # tiene el tipo de evento con sintaxis y semántica explícitamente diseñados en torno al concepto de devoluciones de llamada. Visual Basic tiene su cláusula Handles , que declara explícitamente que un método es una función de devolución de llamada mientras abstrae el concepto de delegados o punteros de función. En estos casos, el concepto semántico de devolución de llamada se integra en el propio lenguaje.

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

En pocas palabras, C ++ tiene lo que necesita para implementar devoluciones de llamada, a menudo de forma bastante sencilla y trivial utilizando punteros de función. Lo que no tiene es palabras clave y características cuya semántica es específica de devoluciones de llamada, como aumentar , emitir , Manejar , evento + = , etc. Si viene de un idioma con esos tipos de elementos, el soporte de devolución de llamada nativo en C ++ Se sentirá castrado.


Scott Meyers da un buen ejemplo:

class GameCharacter; int defaultHealthCalc(const GameCharacter& gc); class GameCharacter { public: typedef std::function<int (const GameCharacter&)> 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 devoluciones de llamada en C ++.


También existe la forma C de hacer devoluciones de llamada: punteros 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 los métodos de clase como devoluciones de llamada, las declaraciones a esos punteros 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); }


Una función de devolución de llamada es un método que se pasa a una rutina y se llama en algún momento 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 las devoluciones de llamada en gran medida.

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


Vea la definición anterior donde indica que una función de devolución de llamada se transfiere a otra función y en algún momento se llama.

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

Aquí es cómo puede utilizar devoluciones de llamada en C ++. Supongamos 4 archivos. Un par de archivos .CPP / .H para cada clase. La 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 devolución de llamada toma 1 parámetro que agregué por el bien de los lectores. El ejemplo no muestra ningún objeto siendo instanciado y usado. 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 la post procesa los datos. Con una función de devolución de llamada, para cada fila de datos leída, la devolución de llamada puede procesarla. Esta técnica elimina la sobrecarga del espacio temporal requerido. Es particularmente útil para consultas SQL que devuelven una gran cantidad de datos que luego deben procesarse 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); }


Nota: la mayoría de las respuestas cubren los punteros de función, que es una posibilidad para lograr la lógica de "devolución de llamada" 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 devolución de llamada es una llamada (ver más abajo) aceptada por una clase o función, utilizada para personalizar la lógica actual en función de esa devolución de llamada.

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

Muchas funciones de la biblioteca de algoritmos estándar <algorithm> utilizan devoluciones de llamada. Por ejemplo, el algoritmo for_each aplica una devolución de llamada unaria a cada elemento en un rango de iteradores:

template<class InputIt, class UnaryFunction> 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 los callables apropiados, por ejemplo:

std::vector<double> 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 a las personas que llaman de ciertos eventos, lo que permite una cierta cantidad de flexibilidad de tiempo de compilación / estática.

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

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

Por lo tanto, el diseñador de la biblioteca no está a cargo de decidir qué sucede con la información que se entrega al programador a través de la devolución de llamada de notificación y no tiene que preocuparse por cómo determinar realmente los valores de la función, ya que la devolución de llamada lógica los proporciona. Hacer esas 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 dinámico en tiempo de ejecución.

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

void player_jump(); void player_crouch(); class game_core { std::array<void(*)(), total_num_keys> 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, por lo tanto, 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)?

Consulte Conceptos de C ++: Se puede cancelar en cppreference para obtener una descripción más formal.

La funcionalidad de devolución de llamada se puede realizar de varias maneras en C ++ (11), ya que varias cosas diferentes se pueden llamar * :

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

* Nota: los punteros a los miembros de datos también se pueden llamar, pero no se llama a ninguna función.

Varias formas importantes de escribir callbacks en detalle

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

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

1. Punteros a funciones

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

Vamos a tener una función simple foo :

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

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

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

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

donde un tipo de puntero de función se verá como

return_type (* name) (parameter_type_1, parameter_type_2, parameter_type_3) // i.e. 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 declaración de 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 alias de tipo y el reconocimiento del tipo de puntero de función también es más fácil

Y una declaración de una función utilizando una devolución de llamada del tipo de puntero de 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 devolución de llamada

La notación de llamada sigue la sintaxis 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 devolución de llamada y tipos compatibles

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

Usar una función que tome una función de devolución de llamada de puntero 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 depende de cómo funciona la devolución de llamada:

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

utilizado 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 función miembro

Un puntero a función miembro (de alguna clase C ) es un tipo especial de puntero a función (e incluso 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 Puntero de escritura 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) // i.e. a pointer to C::foo has the type int (C::*) (int)

donde un puntero nombrado a la función miembro -en analogía al puntero de la función- se verá así:

return_type (T::* name) (parameter_type_1, parameter_type_2, parameter_type_3) // i.e. 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 que toma un puntero al 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 devolución de llamada

El puntero a la función miembro de C puede invocarse, con respecto a un objeto de tipo C mediante el uso de operaciones de acceso de miembros en el puntero sin referencia. 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 sintaxis es equivalente (donde el puntero a C debe estar sin referencia):

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 devolución de llamada y tipos compatibles

Se puede llamar a una función de devolución de llamada que toma un puntero de función miembro de clase T utilizando un puntero de función miembro de clase T

El uso de una función que lleva un puntero al retorno de llamada de la función miembro es, en analogía a los punteros de función, también bastante simple:

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 objects (header <functional> )

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

3.1 Escribiendo un std::function object / type notation

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

std::function<return_type(parameter_type_1, parameter_type_2, parameter_type_3)> // i.e. using the above function declaration of foo: std::function<int(int)> stdf_foo = &foo; // or C::foo: std::function<int(const C&, int)> stdf_C_foo = &C::foo;

3.2 Notación de llamada de devolución de llamada

La clase std::function tiene un operator() definido que se puede usar para invocar su destino.

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

3.3 Notación de uso de devolución de llamada y tipos compatibles

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

3.3.1 Punteros de función y punteros a funciones miembro

Un puntero de funcion

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 expresiones std::bind

Se puede pasar el resultado de una expresión std::bind . Por ejemplo, vinculando 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 las clases que tienen una sobrecarga del operator() apropiado 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

Cambiando el ejemplo del puntero de función para usar la std::function

void stdf_tranform_every_int(int * v, unsigned n, std::function<int(int)> 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 devolución de llamada con plantilla

Al usar plantillas, el código que llama a la devolución de llamada puede ser incluso más general que el uso de objetos std::function .

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

4.1 Escritura (anotaciones de tipo) y llamadas de devolución de llamadas con plantilla

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

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

con una sintaxis aún más general (y más sencilla) para un tipo de devolución de llamada que es un argumento con plantilla simple y pendiente de deducir:

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

Nota: La salida incluida 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, es decir, std::transform , que también tiene una plantilla con respecto a los tipos iterados.

template<class InputIt, class OutputIt, class UnaryOperation> 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 que utilizan devoluciones de llamada con plantilla y tipos compatibles

Los tipos compatibles para el método de devolución de llamada stdf_transform_every_int_templ std::function stdf_transform_every_int_templ son idénticos a los tipos mencionados anteriormente (ver 3.4).

Sin embargo, al usar la versión con plantilla, la firma de la devolución de llamada 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<int,int>(&a[0], 5, &foo); // a == {3, 4, 5, 6, 7} stdf_transform_every_int_templ<int, int const &>(&a[0], 5, &muh); // a == {6, 7, 8, 9, 10} stdf_transform_every_int_templ<int, int &>(&a[0], 5, &woof);

Nota: std_ftransform_every_int (versión sin plantilla; ver más 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 con plantilla simple de transform_every_int_templ puede ser de todos los tipos de llamada 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<int(int)>{&foo}); print_int(a, 5);

El código anterior se imprime:

1 2 3 4 5 transform_every_int_templ <int(*)(int)> 3 4 5 6 7 transform_every_int_templ <int(*)(int&)> 6 8 10 12 14 transform_every_int_templ <int& (*)(int&)> 9 11 13 15 17 transform_every_int_templ <main::{lambda(int)#1} > 27 33 39 45 51 transform_every_int_templ <Meow> 108 132 156 180 204 transform_every_int_templ <std::_Bind<int(*(std::_Placeholder<1>, int))(int, int)>> 975 1191 1407 1623 1839 transform_every_int_templ <std::function<int(int)>> 977 1193 1409 1625 1841

implementación de type_name usada arriba

#include <type_traits> #include <typeinfo> #include <string> #include <memory> #include <cxxabi.h> template <class T> std::string type_name() { typedef typename std::remove_reference<T>::type TR; std::unique_ptr<char, void(*)(void*)> 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<TR>::value) r += " const"; if (std::is_volatile<TR>::value) r += " volatile"; if (std::is_lvalue_reference<T>::value) r += " &"; else if (std::is_rvalue_reference<T>::value) r += " &&"; return r; }


Los singals2 de Boost singals2 permiten suscribir funciones miembro genéricas (¡sin plantillas!) Y de una manera segura para subprocesos.

Ejemplo: las señales de vista de documento se pueden utilizar para implementar arquitecturas de vista de documento flexibles. 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 única señal a la que se conectarán todas las vistas.

class Document { public: typedef boost::signals2::signal<void ()> 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; };

A continuación, podemos comenzar a definir vistas. La siguiente clase de TextView proporciona una vista simple del texto del documento.

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