You are currently browsing the tag archive for the ‘functor’ tag.

¿Qué son functores?

El lenguaje C ofrece punteros a funciones. Estos permiten llamar a diferentes funciones según el valor del puntero. Un puntero así no apunta a un dato sino a un comportamiento – el comportamiento que implementa la función.

Punteros ofrecen, además, apuntar a direcciones de memorias erróneas. Así es posible ejecutar cualquier dato como función. Esto encanta a los autores de código malicioso. Para los demás interesa evitarlo, es decir, como se puede limitar las direcciones a que un puntero de función puede apuntar.

La solución viene con C++. Functores en el lenguaje C++ son clases que tienen sobrecargado el operador(). Esto permite utilizar una instancia de una clase como una función – y sólo una función y no cualquiera.

¿Cómo se usan functores?

El functor más simple es una clase como en este ejemplo:

class MiFunctor
{
public:
    int operator() ()  { return 1; }
};

Este functor es una clase con el método operator() sin parámetros. Nótese que la primera pareja de paréntesis es el nombre del operador y la segunda para los parámetros si los hubiera.

La clase de functor de arriba se puede usar como en el ejemplo siguiente.

MiFunctor miInstancia;
std::cout << miInstancia();

La primera línea crea un objeto del functor y la segunda llama primero al método MiFunctor::operator() y luego imprime el valor de retorno – que es un uno.

Dónde se usan los functores

El método operator() puede tener también parámetros. Como ejemplo definimos un functor que devuelve el menor de dos vectores.

// El tipo de vector que usamos
struct Vector
{
    float x, y;
};

// El functor que compara dos vectores
class VectorMenor
{
public:
    bool operator() (const Vector& a, const Vector& b) const
    {
        // La forma de comparación no importa aquí.
        // Pero para ser completo lo especificamos:
        // devuele a, si la primera coordenada de a es
        // menor o si la primera es igual y la segunda
        // es menor.
        if (a.x < b.x || (a.x == b.x && a.y < b.y))
        {
            return a;
        }
        else
        {
            return b;
        }
    }
};

// Y así usamos el functor
Vector u, v;
bool u_es_menor = VectorMenor(u, v);

Pues bien, matemáticamente no hay una operación menor entre dos vectores. Así ¿para qué sería útil este functor VectorMenor?

Echemos un vistazo a la definición (simplificada) del contendor std::map de la biblioteca estándar de C++.

template <class Key,
          class Value,
          class Compare = std::less<Key> >
class std::map {};

¿Una vez te has preguntado qué signífica este std::less?

Pues, la clase std::map guarda sus elementos ordenados por el atributo – es decir la clase Key. Así no necesita buscar por todos los elementos cuando busca uno en concreto. Comprueba si el atributo del elemento buscado es mayor or menor que el atributo del elemento en medio de todos los elementos. Según el resultado continúa la búsqueda con la mitad inferior o superior. De esta manera se divide entre dos el rango de elementos a buscar tras cada comparación. Esto es mucho más rápido que buscar por todos los elementos.

Para ordenar los elementos por los atributos, la clase std::map require que se pueda aplicar el operador < al tipo del atributo. Este operador existe para los tipos básicos pero no para una clase cualquiera. Por eso, la clase std::map ofrece especificar un functor que determina la “menor” de dos instancias de Key. El método operator() de este functor toma dos argumentos de tipo Key como nuestro ejemplo VectorMenor. El functor de comparación por defecto es std::less – que simplemente usa el operator< global.

Un mapa que guarda cadena de textos ordenados por vectores, se puede definir así:

std::map<Vector, std::string, VectorMenor> mi_mapa;

Nótese que el functor no es una variable que apunta a una función sino un tipo de una variable. Es decir, una clase.

Herencia de functores

Los functores son clases de C++ y, por lo tanto, se pueden definir todos los métodos en ellos como en cualquier otra clase. Y también se pueden heredar.

Un functor derivado puede sobrescribir el método operator() con su propia implementatción. Más interesante es aún declarar un operator() virtual. Así podemos crear una interfaz de functor que se puede implementar de varias formas.

Como ejemplo definimos una interfaz para comparar cadenas de textos en diferentes idiomas. Esto puede ser interesante porque el orden alfabético varia – en español incluso en el mismo idioma: antiguamente se consideró la combinación “ll” como una letra que se ordenó tras la “l” simple. Por eso, la palabra “llamar” vino después de “luna” en los diccionarios antiguos.

// Definimos una interfaz para comparar cadenas de textos.
class InterfazCadenaDeTextoMenor
{
public:
    virtual bool operator() (const std::string& a,
                             const std::string& b) const = 0;
};

La interfaz obliga que el método operator() tenga dos parámetros del tipo std::string y devuelve un bool. El método operator() está declarado const ya que no modifica variables internos de las clase InterfazCadenaDeTextoMenor. No obstante, esto no es obligatorio. Puede haber functores con memoria que, sí, modifican datos internos.

Es importante tener la misma signatura cuando trabajamos con métodos virtuales. Por eso los tipos de retorno y de parámetros deben estar definidos fuera de la clase. Si usaramos InterfazCadenaDeTextoMenor en la clase base como tipo parámetro y ClaseDerivada en la clase derivada, entonces el método virtual no sería sobrescrito, ya que tendría otra signatura.

Boost Bind and std::function

Los punteros de funciones y los functores pueden aparecer igual en el uso.

// Declaración de tipo de un functor
class Functor
{
public:
    void operator() ();
};

// Declaración de tipo de un puntero de función
typedef void (*Puntero)();

// Instancia de un functor y un puntero
Functor functor;
Puntero puntero;

// Uso de un functor y un puntero
functor();
puntero();

Como vemos, tanto el functor como el puntero aparecen con nada más que paréntesis. Para C++ son dos tipos muy distintos, pero para el usuario hacen lo mismo. Por eso vino la idea de crear un super-tipo que une los punteros y los functores en un comportamiento igual. Este existe en la biblioteca Boost con en elemento boost::bind.

Con boost::bind podemos crear un tipo que se inicializa o bien con un puntero de función o bien con un functor. Algo similar es el objeto std::function de la biblioteca de C++ a partir del estándar 2011.

Conclusión

Los functores tienen el mismo uso como punteros de funciones. No obstante ofrecen la seguridad de no ejecutar código cualquiera como puede pasar con punteros mal inicializados. Además, los functores ofrecen todas las capabilidades de clases de C++. En código mixto de punteros y functores se ofrece boost::bind y std::function como tipo común.

Referencias

Lectura adicional

Anuncios

Escribe tu dirección de correo electrónico para suscribirte a este blog, y recibir notificaciones de nuevos mensajes por correo.

Únete a otros 50 seguidores

Archivos

octubre 2017
L M X J V S D
« Ene    
 1
2345678
9101112131415
16171819202122
23242526272829
3031