Publi

Callbacks en C++11 nuevas posibilidades para un software más potente (I)

My beautiful picture

My beautiful picture

Hace tiempo, hice una serie de posts sobre callbacks:

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>

using namespace std;

void hello()
{
  cout << "Hello world!"<<endl;
}

void goodbye()
{
  cout << "Goodbye world"<<endl;
}

int main()
{
  auto fun= hello;

  fun();
  fun = goodbye;
  fun();
  return 0;
}

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>

using namespace std;

struct MyClass
{
  static int hello(string what)
  {
    cout << "Hello "<<what<<"!"<<endl;
    return what.length();
  }

  static int 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;
  return 0;
}

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>

using namespace 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);
    });
  return 0;
}

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>

using namespace 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);

  return 0;
}

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)

También podría interesarte....

There are 5 comments left Ir a comentario

  1. Pingback: Callbacks en C++11 nuevas posibilidades para un software más potente (I) | PlanetaLibre /

  2. Pingback: Callbacks en C++11, llamando a métodos con un objeto asociado (II) – Poesía Binaria /

  3. Pingback: Callbacks en C++11, ejemplos con argumentos por referencia y templates (III) – Poesía Binaria /

  4. Pingback: Cómo buscar en un vector o una lista de mapas en C++ – Poesía Binaria /

  5. OKBet /
    Usando Google Chrome Google Chrome 114.0.0.0 en Windows Windows NT

    Quality posts is the crucial to invite the visitors to visit the web page, that’s what this web page is providing.
    Tournaments Duration of Online Poker

Leave a Reply