Hace tiempo, hice una serie de posts sobre callbacks:
- Callbacks, retrollamadas o delegados o cómo crear código más flexible en C
- C++ Punteros a función miembro (pointer-to-member function) o callbacks con clase
- Callbacks en C++ con boost
Hay algunos posts más, pero se salen del tema (y seguro que salen sugeridos más abajo). El caso es que dejé un poco el tema de lado y me gustaría retomarlo con los cambios de la especificación C++11 (vale, tenemos C++14 lista, pero la versión de 2011 es una de las que más cambios introdujeron (y que también valen para C++14). Aunque se ha escrito mucho sobre el tema, pero desde aquí quiero dar a conocer mi humilde visión.
Serán una serie de posts ya que es un tema muy amplio y me gusta poner gran cantidad de ejemplos.
Una variable que contiene una función
El ejemplo más sencillo, crear una variable que contenga una función, es decir, declaramos una variable y le asignamos como valor una función, así llamamos a la función que tenga asignada la variable en cada momento (podrá cambiar, podremos darle la opción al usuario para que tenga un valor u otro, o nos permitirá abstraer un poco más nuestro código):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <iostream> usingnamespace std; void hello() { cout<<"Hello world!"<<endl; } void goodbye() { cout<<"Goodbye world"<<endl; } int main() { auto fun= hello; fun(); fun = goodbye; fun(); return0; } |
Si usas g++, deberás hacer lo siguiente:
g++ -o simple simple.cpp -std=c++11
El programa, como vemos, asigna a la variable fun (de tipo auto, ya el compilador se encarga de poner el tipo que corresponda, lo cual nos ayuda mucho, que los tipos en C o C++ pueden ser muy complicados de sacar o largos de escribir, en este caso sería un tipo void (fun*)(), cuando tengamos argumentos, ya será otro cantar. Aunque, para hacerlo al más puro estilo C++11, deberíamos declararla como:
1 | std::function<void()> fun=hello; |
(como tenemos using namespace std no volveremos a decir que está ahí, pero es bueno saberlo) y hacer, arriba del todo, un
1 | #include <functional> |
Pero vamos, si vamos a hacer una inicialización inmediata como en el ejemplo, podemos utilizar auto y listo, es más, el archivo generado puede variar, usar function aunque nos ayuda bastante, se queda todo mucho más limpio y después podremos hacer más cosas, no deja de incluir algo de complejidad a nuestro programa.
Luego, como la función goodbye() y la función hello() son iguales, las dos son funciones que no devuelven nada y no tienen argumentos, a fun, le podemos asignar tanto hello, como goodbye, y llamando a fun se ejecutará la función pertinente.
De la misma manera, también funcionaría si hacemos llamadas a funciones estáticas dentro de una clase o un struct:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #include <iostream> #include <string> usingnamespace std; struct MyClass { staticint hello(string what) { cout<<"Hello "<<what<<"!"<<endl; return what.length(); } staticint goodbye(string what) { cout<<"Goodbye "<<what<<"!"<<endl; return what.length(); } }; int main() { auto fun= MyClass::hello; int n; n=fun("world"); fun = MyClass::goodbye; n+=fun("boredom"); cout<<"Total: "<<n<<endl; return0; } |
En este caso, he metido un argumento en cada método y un valor de salida, para que veamos que no hay diferencia en cuanto a la forma de usarlo, bueno, cuando llamamos a fun, tenemos que pasarle el argumento y éste nos dará un valor de salida, pero bueno, con auto, no tenemos que preocuparnos.
La forma de declarar la función en C (pero que no vamos a hacerlo, es sólo por curiosidad) sería:
1 | int(*fun)(string)= MyClass::hello; |
Y, en C++11, utilizando functional lo haríamos de esta forma:
1 | function<int(string)> fun= MyClass::hello; |
Debemos hacerlo así cuando no podamos utilizar auto, es decir, cuando no vayamos a inicializar la variable con ninguna función (y por tanto, el compilador no tenga forma de saber qué tipo queremos darle).
Usando funciones anónimas o lambdas
Podemos incluir la función en línea, es decir, dentro de la propia llamada a la función, es posible implementar una función que no podremos llamar de ninguna manera si no es con la variable a la que la asignamos o con la función a la que llamamos. (Ver ejemplo). Esto es muy útil cuando se trate, por ejemplo de funciones de búsqueda que nos piden una función para comparar o cuando a través de una función producimos una salida (por pantalla o por cualquier otro medio), o cuando debemos pasar a una función la forma con la que queremos tratar los datos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <iostream> #include <string> #include <functional> usingnamespace std; void itera (int desde, int hasta, function<string(int)>llamada) { for(int i=desde; i< hasta;++i) cout<< llamada(i)<< endl; } int main() { itera(1, 10, [](int n) { return"El cuadrado de "+std::to_string(n)+" es "+std::to_string(n*n); }); return0; } |
La salida de esto será:
El cuadrado de 1 es 1
El cuadrado de 2 es 4
El cuadrado de 3 es 9
El cuadrado de 4 es 16
El cuadrado de 5 es 25
El cuadrado de 6 es 36
El cuadrado de 7 es 49
El cuadrado de 8 es 64
El cuadrado de 9 es 81
En este caso, la función itera hace un bucle empezando en desde y terminando en hasta-1 y en cada iteración ejecutará la función llamada, dicha función, se la hemos pasado en main() a través de una función anónima. Como vemos empezará en [](tipo valor,…) con tantos argumentos como tengamos. La salida, en este caso, la averiguará el compilador automáticamente, aunque si queremos imponer un tipo de salida, lo podremos hacer así:
1 2 3 4 | itera(1, 10, [](int n)-> string { return"El cuadrado de "+std::to_string(n)+" es "+std::to_string(n*n); }); |
Otro ejemplo, utilizando la biblioteca algorithm (std), ordenando los valores de un vector, utilizando para ordenar una función anónima (para que podamos elegir el criterio de ordenación que queramos):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include <iostream> #include <vector> #include <algorithm> usingnamespace std; void verVector(vector<int> v) { cout<<"Vector v: "; for(auto i : v) { cout<< i <<"\t"; } cout<< endl; } int main() { vector<int> v ={9, 4, -5, -10, 15, 1, -4, 5}; verVector(v); cout<<"Ordenación 1"<<endl; sort(v.begin(), v.end(), [](int x, int y) { return((x*x)<(y*y)); }); verVector(v); cout<<"Ordenación 2"<<endl; sort(v.begin(), v.end(), [](int x, int y) { return((x*x)>(y*y)); }); verVector(v); return0; } |
Las funciones anónimas tienen más cosas que trataremos más adelante, junto con muchos más ejemplos sobre callbacks en C++11 y posibilidades para nuestros programas. Por el momento, lo dejamos aquí hasta la próxima entrega (el día 9 de noviembre de 2015), en el que trataremos el tema de las llamadas a métodos de una clase. Esta vez, no serán métodos estáticos, sino que estarán asociados a un objeto.
Foto: Aaron (Flickr-cc)
The post Callbacks en C++11 nuevas posibilidades para un software más potente (I) appeared first on Poesía Binaria.