Archivo

Posts Tagged ‘Algoritmos’

Functores y objetos llamables en la librería estándar de C++


Hace algunos días escribí acerca de los lambdas de C++ y C#. En esa oportunidad hice un recuento sobre las opciones que en C++ existen para tratar funciones como parámetros, o en general, poder tener “objetos llamables” como variables. En particular, exploramos tres alternativas:

1.- Funciones de retro-llamada. También llamados “callbacks”, estas funciones son los famosos punteros a void* (o al tipo de dato que retornan).

void print(const string& str)
{
    cout << str << endl;
}

void foo()
{
    void (*func)(const string& str);
    func = &print;
    func("Hola mundo!");
}

2.- Functores. Estas son clases que implementan el operador (), de tal suerte que simulan ser funciones, aunque en realidad sean clases.

class printer
{
    public:
        string str;

        printer() { };
        printer(const string& msg) : str(msg) { }

        void operator()() { 
            cout <str <str = msg;
            cout <str << endl;
        }
};

int main()
{
    printer func;
    func("Hola mundo!");

    func.str = "Hallo Welt!";
    func();

    return 0;
}

3.- Lambdas. Como comentamos, éstas son funciones anónimas cuyo cuerpo se define al momento, i.e. dentro de otra función.

auto func = [](const string& str) {
    cout << str << endl;
};

func("Hola mundo!");

Pues bien, hay una opción de la que no he hablado. En esencia no se diferencia mucho de un functor. O, mejor dicho, es un functor. O aún mejor dicho, es una plantilla que define un objeto que puede ser llamado, como una función o functor. ¡Argh! Me refiero a la clase function, definida en <functional>.

Esta clase es una plantilla cuyo parámetro es –escúchalo bien- la definición de una funcion. Es decir, su firma. Y obvio con esa firma es con la que se define el operador (). Por cierto, el operador de asignación está sobrecargado para recibir la función a ejecutar. Así es, a function de todas formas le tienes que pasar una función como parámetro. Veamos.

#include <iostream>
#include <functional>

using std::cout;
using std::endl;
using std::function;

int sqr(int a)
{
    return a * a;
}

int main(int argc, char* argv[])
{
    function f;
    f = sqr;
    cout << f(5) << endl;

    system("pause");
    return 0;
}

Obvio se imprime “25” en pantalla. En la primera línea definimos nuestra función con parámetro de plantilla int(int). Esto quiere decir que la función tendrá un parámetro de tipo entero y que regresa otro entero. La segunda línea asigna la función sqr (que cumple con la firma de la plantilla). La tercera línea simplemente invoca la función.

Si creas una función, no le asignas la referencia e intentas invocarla, te lanzará una excepción de tipo bad_function_call.

try
{
    function<int(int)> f;
    int a = f(5); // tsssss!
    cout << a << endl;
}
catch (const std::bad_function_call& ex)
{
    cout << ex.what() << endl;
}

Lo mejor: ¡también podemos usar lambdas!

typedef function<int(int, int)> bin_function;

bin_function suma;
suma = [](int a, int b) { return a + b; };

bin_function resta;
resta = [](int a, int b) { return a - b; };

cout << suma(31, 11) << endl;
cout << resta(67, 25) << endl;

Ahora bien, ¿qué ventaja tiene function<T> sobre usar lambdas? Pues que por un lado hace innecesario tener que crear nuestros propios functores de nuevo, y por otro lado, podemos usarlo como parámetro. Por ejemplo, para crear eventos o una implementación del patrón “observer”.

Veamos. Primero nos creamos una clase sencilla: empleado, la cual tiene tres miembros: para nombre, edad y domicilio. Privados. Luego creamos un par de métodos para cada una de éstas: uno que obtiene el dato y otro que lo escribe. Lo que queremos es que cada vez que alguna de estas tres propiedades se actualice, si el dato es diferente, entonces así lo notifique a quien quiera “escucharlo”. Al final, es es lo que hace el patrón de diseño observer. En fin, para ello creamos un método al cual se le llamará cuando cambien las propiedades. Algo así.

class employee
{
    public:
        employee() { }

        string name() const { return _name; }
        string address() const { return _address; }
        int age() const { return _age; }

        void name(const string& value) {
            if (value != _name) {
                _name = value;
                on_property_changed("name");
            }
        }

        void address(const string& value) {
            if (value != _address) {
                _address = value;
                on_property_changed("address");
            }
        }

        void age(int value) {
            if (value != _age) {
                _age = value;
                on_property_changed("age");
            }
        }
    protected:
        void on_property_changed(const string& property_name)
        {
        }

    private:
        string _name;
        string _address;
        int _age;
};

Pero ¿qué ponemos en on_property_changed? Lo que queremos es avisar que el objeto ha sido actualizado, así que esta función debería llamar a los “escuchas” o “listeners” pasándole una referencia a sí mismo. Luego, queremos pasar el nombre de la propiedad que ha cambiado. Para todo esto, quizás sea mejor crear una clase: la llamaremos event_args.

class event_args
{
    public:
        event_args(const employee& sender, const string& property_name)
            : _sender(sender), _property_name(property_name)
        {
        }

        const employee& sender() const { return _sender; }
        string property_name() const { return _property_name; }

    private:
        const employee& _sender;
        string _property_name;
};

Y ahora, nos basta un par de typedefs:  uno que defina una función (std::function<T>, recuerda) que tome como parámetro un objeto de tipo event_args, y luego, un contenedor de este tipo de funciones, como –digamos- std::list.

typedef function<void(event_args&)> event_function;
typedef list<event_function> event_listeners;

Ahora en la clase employee nos creamos un miembro (público, por facilidad) de tipo event_listeners…

event_listeners listeners;

…y procedemos a escribir, ahora sí, nuestra función on_property_changed, que recorra cada función de la lista y la invoque. Easy peasy.

void on_property_changed(const string& property_name)
{
    event_args args(*this, property_name);
            
    auto callback = [&args](event_function& func) { func(args); };
    for_each(listeners.begin(), listeners.end(), callback);
}

Bueno bueno, usamos un lambda y la función std::for_each, pero meh, es lo mismo. Y con esto ya tenemos listo nuestro patrón “observer”, y sólo resta el usarlo: primero creamos una función que imprime el nombre de la propiedad que ha sido modificada. Luego, hacemos otra función que haga ciertas validaciones (nombre no vacío, edad mayor de 16 años, etc.) e imprima un mensaje si no se cumplen. Luego instanciamos nuestro objeto employee y añadiremos las funcinoes a la propiedad listeners.

int main(int argc, char* argv[])
{
    auto print_func = [](event_args& args) { 
        cout << "La propiedad [" 
             << args.property_name()
             << "] ha cambiado. "
             << endl;
    };

    auto validate_func = [](event_args& args) {
        auto sender = args.sender();
        if (sender.name().length() <= 0)
            cout << "El nombre no puede estar vacío." << endl;
        if (sender.age() < 16)
            cout << "La edad mínima laboral es de 16 años." <= 255)
            cout << "La dirección es muy larga." << endl;
        cout << endl;
    };

    employee emp;
    emp.listeners.push_back(print_func);
    emp.listeners.push_back(validate_func);
    emp.name("Fernando");
    emp.name("");
    emp.name("Fernando Gómez");
    emp.age(29);
    emp.address("Algún lugar en la Ciudad de México.");

    system("pause");
    return 0;
}

Al ejecutarse, este programa tiene esta salida:

La propiedad [name] ha cambiado.
La edad mφnima laboral es de 16 a±os.

La propiedad [name] ha cambiado.
El nombre no puede estar vacφo.
La edad mφnima laboral es de 16 a±os.

La propiedad [name] ha cambiado.
La edad mφnima laboral es de 16 a±os.

La propiedad [age] ha cambiado.

La propiedad [address] ha cambiado.

Press any key to continue . . .

¡Así sí gana la gente!

Hemos visto en el pasado cómo usar diferentes técnicas para llamar funciones como variables o parámetros. Y ahora hemos visto cómo podemos combinarlas, junto con esta nueva función exclusiva de C++ 11, para lograr estructuras más complejas. No es que antes no pudieran hacerse, pero ahora no tenemos que escribir tanto código. Y al final de todo es es lo que debe proveer una buena librería, ¿o no?

Por último, vale la pena comentar que esto es apenas un vistazo a std::function y, sobre todo, a todos los objetos y funciones contenidos dentro del archivo <functional>. Poco a poco iremos explorando más posibilidades, así que ¡sigue en contacto!

¡Hasta la próxima!

Anuncios

Convertir un número a representación binaria


Un problema que suele ser recurrente para los estudiantes es el saber cómo convertir un número en su representación binaria. Cabe recordar que un número, por definición, es binario. ¿Qué hace este ejemplo? Pues muestra su representación en unos y ceros, pero en una cadena de texto.

La clase que presento a continuación, binario, es una plantilla que toma cualquier tipo de dato que se desee convertir. La clase presenta un solo miembro privado, _t, de tipo parametrizado, cuyo valor será convertido a una cadena de texto con su representación binaria. A su vez, la clase tiene tres constructores: un constructor por default, un constructor copia y un constructor que inicializa el valor. La clase también tiene un método para obtener el valor y uno para establecerlo, por si no se quiere utilizar el constructor.

Pero lo verdaderamente interesante pasa en el método convertir. Este método hace tres cosas. En primer lugar, toma los bytes del objeto a convertir (i.e. _t) y los guarda en un búfer de memoria. Luego, para cada byte del arreglo, va evaluando cada uno de los bits que lo conforman, y pregunta si el bit es uno o cero, e inserta una representación de texto en una variable, que al final tendrá el valor resuelto. Esto lo podemos ver en el siguiente fragmento:

while (actual != 0)
{
    if (actual & 1) 
        str.insert(0, "1");
    else
        str.insert(0, "0");

    actual >>= 1; 
}

La variable actual guarda el byte actual que se está evaluando. El if hace una conjunción a nivel de bits y pregunta si el bit de hasta la derecha está activo o no. Por ejemplo, suponiendo que actual sea un 3, cuyo binario es 1 1, entonces la operación sería:

    1 1
  & 0 1
 --------
    0 1

Lo cual regresaría una expresión verdadera. Ahora, si en lugar de 3 fuera un 4, tendríamos:

    1 0 0
  & 0 0 1
 ----------
    0 0 0

Lo cuál nos regresaría una expresión falsa. Finalmente, después del if, desplazamos el valor un bit a la derecha. Así, si teníamos un 1 1, desplazando a la derecha tenemos un 0 1; y si tenemos un 1 0 0, entonces ahora tendríamos un 0 1 0. Cuando ya no queden bits, todo se evaluará a cero y se saldrá del bucle.

He aquí el código completo de la clase.

using std::string;
using std::memcpy;

template<class T>
class binario
{
    private:
        T _t;

    public:
        binario()
        {
        }

        binario(const T& t)
        {
            _t = t;
        }

        binario(const binario& convertidor)
        {
            _t = convertidor._t;
        }

        virtual ~binario()
        {
        }

        const T& valor() const
        {
            return _t;
        }

        void valor(const T& t)
        {
            _t = t;
        }

        virtual string convertir() const
        {
            unsigned char* bufer;
            unsigned char actual;
            size_t size;
            string str;

            // convertimos los bytes que tenga el objeto a serializar
            // en un búfer de memoria, copiando byte por byte.
            size = sizeof(T);
            bufer = new unsigned char[size];
            memcpy(bufer, &_t, sizeof(T));

            // cada elemento del búfer es un byte, por lo que tendremos
            // que ir convirtiendo byte por byte. el bucle for siguiente
            // recorre todos los bytes en el búfer.
            for (size_t i = 0; i < size; i++)
            {
                // el byte actual
                actual = bufer[i];

                // un byte tiene, por lo general, ocho bits. lo que
                // hacemos en el siguiente bucle es simular que recorremos
                // bit por bit, para ver si es un uno o un cero. para esto
                // empleamos el operador lógico "y" a nivel de bit, y 
                // preguntamos si el bit de más a la derecha es un uno
                // (en el if). de ser así, lo guardamos en la cadena de
                // texto como "1", y si no, como "0". finalmente, 
                // desplazamos los bits del byte una posición a la
                // derecha, de tal suerte que nos deshacemos del bit
                // ya evaluado y procedemos a evaluar el siguiente bit.
                // cuando ya no queden bits, el byte tendrá un valor
                // de cero y salimos del bucle, para evaluar el 
                // siguiente byte.
                while (actual != 0)
                {
                    if (actual & 1) // ¿el bit de la derecha es un uno?
                        str.insert(0, "1");
                    else
                        str.insert(0, "0");

                    // desplazamos los bits un espacio a la derecha
                    // para deshacernos del bit actualmente evaluado
                    actual >>= 1; 
                }
            }

            delete [] bufer;
            
            return str;
        }
};

El siguiente código es un programita que utiliza dicha clase para obtener la representación binaria de cualquier argumento.


#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include "binario.h"

using std::cout;
using std::cin;
using std::endl;
using std::string;

struct persona
{
    char nombre[20];
    char puesto[20];
    int edad;
    double salario;
};

int main()
{
    binario<int> convEntero;
    binario<char> convCaracter;
    binario<double> convDecimal;
    binario<persona> convPersona;
    string comando;
    int rangoInicial;
    int rangoFinal;
    double decimal;
    Persona persona;

    cout << "***** Un rango de enteros *****" << endl;
    cout << "Escribe el rango inicial: ";
    cin >> rangoInicial;
    cout << "Escribe el rango final: ";
    cin >> rangoFinal;
    cout << endl;
    for (int i = rangoInicial; i <= rangoFinal; i++)
    {
        convEntero.valor(i);
        cout << " El valor para " << convEntero.valor() << " es: " << convEntero.convertir() << endl;
    }
    system("pause");
    cout << endl;

    cout << "***** Una cadena de texto *****" << endl;
    cout << "Escribe una cadena: " << endl;
    cin >> comando;
    for (size_t i = 0; i < comando.size(); i++)
    {
        convCaracter.valor(comando.at(i));
        cout << " " << convCaracter.convertir();
    }
    cout << endl;
    system("pause");
    cout << endl;

    cout << "***** Un valor decimal *****" << endl;
    cout << "Escribe un valor decimal: " << endl;
    cin >> decimal;
    convDecimal.valor(decimal);
    cout << convDecimal.convertir() << endl;
    cout << endl;
    system("pause");
    cout << endl;

    cout << "***** Una estructura plana cualquiera *****" << endl;
    strcpy(persona.nombre, "fernando");
    persona.edad = 27;
    strcpy(persona.puesto, "programador");
    persona.salario = 25751.75;
    convPersona.valor(persona);
    cout << convPersona.convertir() << endl;
    cout << endl;
    system("pause");
    cout << endl;

    return 0;
}


Categorías:C++, Cómo hacer, Independiente Etiquetas: