Archivo

Posts Tagged ‘ISO C++’

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!

Boostificando mi vida


Vaya cosas de la vida. La gente que me conoce sabe que soy C++ boy desde hace años: no por nada mis sobrenombres son Fer++ o F++.

Aunque no fue el primer lenguaje que aprendí (ese fue QBASIC y luego Visual Basic 6, cuando iba en la preparatoria), fue mi primer lenguaje profesional. Antes de salir de la preparatoria ya había hecho trabajos con HTML, JavaScript y CSS, pero mi primer trabajo para una empresa de desarrollo fue como programador de C++: cuando apenas tenía 19 años en marzo de 2002. Desde ahí nunca he dejado de programar con este lenguaje.

Seguro, el boom de .NET llegó a México alrededor del 2004, y hubo altibajos, pero en general creo que fui bastante constante. En 2008 fue cuando ahora sí mi trabajo se volvió puro .NET con algo de SharePoint. Sin embargo, ahora que el código no administrado viene de vuelta, con todo, me emociona volver a usar este lenguaje y de hecho ya estoy aprendiendo Metro, la nueva API de Microsoft para Windows 8. Y no soy el único: justo hoy se está llevando a cabo la conferencia Going Native 2012.

¿Cuántos libros de C++ no habré leído con entusiasmo? The Standard C++ Library, de Nicolai Josuttis, o qué tal Modern C++ Design de Andrei Alexandrescu… He probado varias librerías: obviamente MFC, pero también la Windows Template Library, Qt o incluso wxWidgets. Y sin embargo… nunca había utilizado Boost.

Boost, para quien no sepa, es una librería, o conjunto de librerías de C++, diseñadas para integrarse bien con el estándar de C++. Proveen mucha funcionalidad que el estándar todavía no contempla, y éstas son revisadas por mucha gente de la comunidad, incluídos participantes del comité de estandarización de C++. Es más, muchos componentes de Boost suelen terminar en el estándar. Pero no dejes que te lo cuente, entra a este enlace y entérate por tí mismo.

Ahora, no es que no supiera de la existencia de Boost. Hace algunos años intenté instalarlo pero la verdad que fue un poco engorroso el construirlo. La mayoría de los componentes de Boost son puros encabezados, perohay ciertaspartes que requieren enlazar con librerías estáticas, y ahí es donde estaba el problema.

Y sucede que con todo este fervor por regresar al mundo nativo, en parte promovido por Microsoft y su Windows 8, ahora sí decidí echarle un ojo. Me encontré conque ya tenían un instalador, así que ésta fue bien fácil. Sólo tuve que agregar el directorio de instalación a

image

Y ahora sí, estamos listos para usar Boost. Probemos algo sencillo.


#include <iostream>
#include <boost/array.hpp>

int main(int argc, char* argv[])
{
    boost::array a;
    for (auto i = 0; i < a.size(); i++)
        a[i] = i * i;

    for (auto i = a.begin(); i != a.end(); ++i)
        std::cout << *i << std::endl;

    system("pause");
    return 0;
}

 

La clase boost::array en realidad es un simple envoltorio de un array natural, pero provee ciertas características adicionales como el contar con iteradores begin() y end(). En fin, no importa. Como estas clases, Boost cuenta con muchísimas más.

Y he de decir que hasta donde he podido explorar he encontrado muchas clases muuuuuuucho muy útiles. De hecho poco a poco las iré exponiendo por aquí. Pero venga, anímate tú también y boostifica tu vida.

¡Nos vemos!

Categorías:Boost, C++, Noticias Etiquetas: ,

Comparando lambdas entre C++ y C#


Ahora que me ha dado por explorar las nuevas características de C++ 11, muchas de las cuales han sido ya incorporadas a Visual C++ 10, entre otras cosas me he puesto a explorar los lambdas de C++. Y naturalmente, tras mucho tiempo de trabajar con C# y .NET surge la inquietud/curiosidad de compararlas. Así, comencemos a revisar un poco de historia.

Funciones de retro-llamada (callbacks)

En la era antigua, cuando un programador de C++ quería pasar una función como parámetro, sacaba su cincel y tallaba en piedra un código similar al que sigue:



#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <list>

using namespace std;

class employee
{
    public:
        employee()
            : _age(0), notify_changes(nullptr)
        {
        }

        employee(const string& name, int age, double salary)
            : _name(name), _age(0), notify_changes(nullptr)
        {
        }

        employee(const employee& copy)
            : _name(copy._name), _age(copy._age), notify_changes(nullptr)
        {
        }

        string name() const { return _name; }
        int age() const { return _age; }
        
        void (*notify_changes)(const string& msg);

        void name(const string& value)
        {
            if (_name != value)
            {
                _name = value;
                if (notify_changes != nullptr)
                {
                    string msg = "Name ha cambiado, nuevo valor: " + value;
                    notify_changes(msg);
                }
            }
        }

        void age(int value)
        {
            if (_age != value)
            {
                _age = value;
                if (notify_changes != nullptr)
                {
                    stringstream msg;
                    msg << "Age ha cambiado, nuevo valor: " << value;
                    notify_changes(msg.str());
                }
            }
        }

    private:
        string _name;
        int _age;
};

void print(const string& msg)
{
    cout << "\nRecibiendo mensaje: " << "\n\t" << msg << endl;
}

int main(int argc, char* argv[])
{
    employee emp;
    emp.notify_changes = &print;

    emp.name("Fernando Gómez. ");
    emp.age(28);

    system("pause");
    return 0;
}

Este código muestra un básico patrón del observador, en el cual una entidad externa al objeto observado recibe “notificaciones” o actualizaciones por parte de éste. En este caso tenemos una clase employee que “notifica” cuando alguna de sus propiedades cambia. Para la notificación se expone un puntero a función (también llamados callbacks o, intentando traducir, función de retro-llamada), llamado notify_changes. Pueden ver la sintaxis que es un poco engorrosa. Así, cuando name o age cambian, invocamos el puntero a la función, si éste no es nulo.

Podemos ver que en main(), es simple cuestión de asignar al puntero una función, en este caso una referencia a print. Tras esto, cualquier invocación a name() o age() hará que print() sea ejecutada.

image

Así pues, esta es la forma en la que hacíamos las cosas con C++ hace años. Por cierto que podemos usar plantillas para que la cosa no quede tan fea. Así fue como muchos elementos de la biblioteca estándar de C++ fueron implementados.

Functores

Como puedes ver del ejemplo anterior, los callbacks proveen ciertas dificultades. De entrada la sintaxis, pero además la falta de poder usar polimorfismo y otras herramientas del lenguaje.

Ante esto, comenzó a desarrollarse un idioma en C++, el cual consiste en sobrecargar el operador () para tratar clases como si fueran funciones. A este tipo de construcción se le llamó un functor. Y son perfectos para utilizarse con plantillas.

class functor
{
    public:
        int a; int b;

        void operator(const string& msg) const
        {
            cout << msg << a + b << endl;
        }
};

...

functor func;
func.a = 5;
func.b = 10;
func("Resultado de suma: ");

El código anterior muestra un functor básico. Dado que es una clase podemos hacer todo lo que hacemos con clases: tener estado, otros métodos y propiedades, atributos, herencia, polimorfismo… Mucho mejor que un simple callback.

Eventos y delegados en .NET

Cuando llega .NET una de las cosas que más recuerdo era la posibilidad de crear callbacks de forma fácil, fuertemente tipados y siendo menos propensos a errores. Esta característica de C# fue la que más llamó mi atención. Aunque, por supuesto, en .NET no se llaman callbacks, sino delegados.

public delegate void NotifyChanges(string msg);
...

public void Print(string msg)
{
    Console.WriteLine("Recibiendo mensaje: " + msg);
}
...

NotifyChanges miCallback = new NotifyChanges(Print);
miCallback("¡Hola mundo!");

Del código anterior la primera línea declara un delegado. Es similar a la declaración de un callback de C++, pero quitando la sintaxis horrible. Luego mostramos un método cualquiera, similar al print anterior, y por último mostramos cómo incializar una variable delegado y hacer que apunte hacia el método Print anterior.

Los eventos son semánticamente diferentes a los delegados, pero técnicamente son prácticamente lo mismo. Para declarar un evento (dentro de una clase o interfaz) necesitamos usar la palabra reservada event. Por lo demás, los invocamos como delegados. Así, podemos replicar el código del empleado anterior en C#.

using System;

public delegate void NotifyFunc(string msg);

public class Employee
{
    public Employee(string name, int age)        
    {
        _name = name;
        _age = age;
    }

    public Employee()
        : this(string.Empty, 0)
    {
    }

    public event NotifyFunc Notify;

    protected virtual void OnNotify(string msg)
    {
        if (Notify != null)
            Notify(msg);
    }

    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                _name = value;
                OnNotify("Name ha cambiado, nuevo valor: " + value);
            }
        }
    }

    public int Age
    {
        get { return _age; }
        set
        {
            if (_age != value)
            {
                _age = value;
                OnNotify("Age ha cambiado, nuevo valor: " + value);
            }
        }
    }

    private string _name;
    private int _age;
}

static class Program
{
    static void Print(string msg)
    {
        Console.WriteLine("Recibiendo mensaje: " + msg);
    }

    static void Main(string[] args)
    {
        Employee emp = new Employee();
        emp.Notify += Print;
        emp.Name = "Fernando Gómez. ";
        emp.Age = 28;

        Console.ReadKey(true);
    }
}

La utilización de delegados se ha extendido mucho en el mundo de C# y .NET. Las interfases gráficas es quizás donde más se emplean. Pero también otros lados. Por ejemplo, la clase List<T> tiene un método Find, que toma un predicado como parámetro, un delegado que regresa true o false, dependiendo de si es el elemento buscado.


bool FindByName(Employe emp)
{
    return emp.Name == "Fernando";
}

List list = new List();
...
Predicate func = new Predicate(FindByName);
Employee found = list.Find(func);

 

Ahora bien, desde hace alguans versiones de .NET exsiten los delegados anónimos. Éstos nos permiten declarar funciones dentro del cuerpo de otro método. Así, el ejemplo anterior podríamos reescribirlo como:

List<Employee> list = new List<Employee>();
...

Employee found = list.Find(new delegate(Employee emp) {
        return emp.Name == "Fernando";
    });

 

Lambdas en .NET

Como hemos podido ver, los delegados ayudan en muchas situaciones, como en la creación de eventos, la implementación de ciertos patrones de diseño y como en situaciones como el List<T>.Find. Sin embargo, la sintaxis no es muy cómoda aun.

Del cálculo lambda, aquella rama de las matemáticas que se dedica a investigar la definición de función, aplicación de funciones y recursión, surge el concepto de funciones lambda. Éstas son, en esencia, declaración de funciones anónimas (en .NET delegados anónimos) utilizando una sintaxis similar. Una función, por ejemplo, indica su parámetro seguido de una flecha, donde se indica el resultado. Así, f(x) = x*x podemos reescribirla como: x –> x*x usando dicha notación.

Pues bien, en C# así se implementan. La siguiente declaración crea un delegado anónimo que toma un número como parámetro y regresa su cuadrado.

Func<int, int> func = x => x * x;

Por supuesto, podemos usar un lambda para crear un delegado que tome como parámetro un objeto de tipo Employee y nos regrese un valor booleano:

var func = x => x.Name = "Fernando";

Ahm… bueno, la verdad es que el código anterior no funciona… Con una declaración así, el compilador de C# no puede saber el tipo de la variable x. Pero podemos cambiarlo de tal  forma que sí lo infiera. Por ejemplo, un delegado de tipo Predicate concuerda con la definición de lo que queremos hacer.

Predicate<Employee> func = x => x.Name == "Fernando";

De esta forma el compilador infiere el tipo del delegado. Funciona igual con los parámetros de un método: gracias a esto podemos usar un lambda directamente en nuestro método Find:

List<Employee> list = new List<Employee>();
...

Employee found = list.Find(x => x.Name == "Fernando");

Mucho mejor, ¿no?

Ahora bien, ¿qué pasa si necesitamos más parámetros o ejecutar más acciones? Pues usamos paréntesis y llaves. Por ejemplo, el método List<T>.Sort pide un delegado que tome dos parámetros de entrada y los compare.

List<Employee> list = new List<Employee>();
...

list.Sort( (x, y) => x.Name.CompareTo(y.Name));

Si quisiéramos usar un lambda que además escriba la comparación en la consola, haríamos algo así:

List<Employee> list = new List<Employee>();
...

list.Sort( (x, y) => {
    Console.WriteLine("Comparando: {0} vs {1}", x.Name, y.Name);
    return x.Name.CompareTo(y.Name);
});

Hay otra característica de las funciones lambdas que no hemos mencionado: la capacidad para acceder a variables que están al alcance. Por ejemplo, regresemos al ejemplo de la búsqueda. Quizás en lugar de comparar contra “Fernando” queremos comparar contra el valor de alguna variable. En ese caso, es legal hacer esto:

List<Employee> list = new List<Employee>();
...

string name = "Fernando";
Employee found = list.Find(x => x.Name == name);

La función lamda está accediendo a una variable que está declarada fuera de su cuerpo. Por supuesto, el compilador CLR, internamente, al crear el delegado anónimo, detecta que la variable externa es utilizada dentro del lambda y genera una función con un parámetro adicional. Dicha variable externa siempre se pasará por referencia. Ésto último podemos probarlo de forma sencilla:

Int32 i = 0;
Action func = () => i++;
func();
Console.WriteLine(i);

Cuando ejecutamos este código, se imprime “1”. Action es un delegado sin parámetros y con tipo de retorno void. Creamos un lambda: () indica que no hay parámetros, y simplemente aumentamos en uno la variable externa. E Int32 es una estructura, que se pasaría por valor en cualquier función normal. No en este caso.

Lambdas en C++

Así, ahora que estamos al borde de la versión 4.5 de .NET, vemos cómo los lambdas de .NET han simplificado el desarrollo, y son en esencia azúcar sintáctica bastante útil. Pues bien, dada su popoularidad (no sólo en .NET, sino también en el mundo de la programación funcional, en lenguajes como OCaml o incluso el reciente F#), hace tiempo que el comité que aprueba el estándar de C++ ha trabajado para incorporar lambdas a este lenguaje. Finalmente se han aceptado y ya están definidos en la versión del estándar C++ de 2011.

Afortunadamente para quienes usamos herramientas Microsoft, nuestro querido y no siempre bien ponderado Visual C++ 2010 ¡ya incluye esta característica! Así que ya podemos usar lambdas en C++. Aunque a fuer de ser sincero, los lambdas de C++ tienen una sintaxis… ehm… compleja, digamos.

Vamos por partes. Para declarar un lambda comenzamos por poner unos corchetes. Acto seguido, entre paréntesis listamos los parámetros como los de cualquier función. Luego, entre llaves, va el cuerpo del método. Algunos ejemplos.

auto func1 = []() { };
func1();

auto func2 = []() { cout << "Hola mundo!" << endl; };
func2(); // imprime el Hola mundo!

auto func3 = [](int i) { return i * i; };
int sqr = func3(5); // regresa 25
cout << sqr << endl;

auto func4 = [](list<int>& list) {
    for (auto i = list.begin(); i != list.end(); ++i)
        cout << *i << endl;
};

auto func5 = [](list<int>& list) {
    for (int i = 1; i <= 10; i++)
        list.push_back(i*i);
};
list<int> l;
func5(l);
func4(l);

En este ejemplo creamos cinco funciones lambdas. Nos apoyamos en la palabra reservada “auto” para determinar el tipo de variable con base en el tipo de la asignación (igual al “var” de C#).

La primera función, func1, no hace nada: no tiene parámetros, no tiene valor de retorno ni haca absolutamente nada: posiblemente el lambda más sencillo que podamos crear. Por su parte, func2 imprime en consola el tradicional “Hola mundo!”. Esta lambda no tiene parámetros ni tipo de retorno (es decir, es “void”), pero sí tiene cuerpo. Con func3 la cosa ya se pone más interesante: toma un parámetro y regresa el cuadrado del mismo.

La lambda func4 está más interesante aún: toma una referencia a una lisat e imprime en consola todos sus elementos. Adicionalmente, func5 toma una lista también y le inserta el cuadrado de los primeros diez números naturales. Luego nos creamos una lista y la pasamos a func5 para que genere los números y luego a func4 para que los imprima.

Ahora bien, te preguntarás: ¿por qué no mejor creamos la lista y que el lambda haga referencia a ésta, sin tener que pasarla como parámetro? ¡Buena idea!

list<int> l;
auto func = [](int min, int max) {
    for (int i = min; i < max; i++)
        l.push_back(i*i);
};
func(1, 10);

Similar a C#, ¿no? Sin embargo, al compilar… ¡tómala! Aparece este mensaje de error:

'l' cannot be implicitly captured because no default capture mode has 
been specified.

Changos. ¿Por qué no compila? Cuando veíamos la versión de C# dijimos que el compilador del CLR pasa en automático las variables que están fuera del lambda. En C++ no se hace en automático: hay que especificarle al compilador cómo queremos pasar las variables externas. Y (por si te lo habías preguntado) aquí es donde entran en juego los corchetes.

En efecto, dentro de los corchetes especificamos qué parámetros externos usaremos, y cómo queremos pasarlos a la función lambda. Hasta ahora hemos dejado corchetes vacíos, que quiere decir: no pases ninguna variable externa. Ahora lo que haremos será añadir un ámperson adentro, lo cual quiere decir: pasa todas las variables por referencia.

list<int> l;
auto func = [&](int min, int max) {
    for (int i = min; i < max; i++)
        l.push_back(i*i);
};
func(1, 10);

Como puedes ver en la línea resaltada, hemos añadido el ámperson. Así, estamos pasando todas las variables externas por referencia, y por ende “l” se pasa así. Ya no tenemos problema.

Ahora bien, supongamos que cambiamos la definición del lambda y tanto min como max serán variables externas. Pero éstas no queremos pasarlas por referencia, sino por valor. Lo que hacemos es enumerar entre los corchetes la lista de parámetros externos, separados por coma, y poner el nombre de la variable en cuestión.

list<int> l;
int min = 1;
int max = 10;
auto func = [&l, min, max]() {
    for (int i = min; i < max; i++)
        l.push_back(i*i);
};
func();

Como puedes ver en el ejemplo, estamos pasando la lista por referencia, mientras que el rango min-max lo pasamos por valor.

Por último, hay que recordar que los lambdas generan un tipo de dato bien definido. Por ello, podemos usarlos incluso como parámetros de plantillas. De esto mismo ya hemos hablado en una entrada anterior.

Comparando

A lo largo de esta entrada hemos visto cómo históricamente ha sido necesario tratar las funciones como si fueran variables. Al principio teníamos callbacks, y luego en el mundo de .NET tuvimos delegados. Luego vimos cómo éstos evolucionaron en delegados anónimos y finalmente en lambdas. Y por último, vimos cómo C++ se puso al corriente en este tema.

Tras este recorrido, me gustaría señalar algunas similitudes y diferencias que he encontrado entre ambas implementaciones. Sería interesante que si conoces alguna que no esté listada, ¡me la hagas llegar para exponerla y platicarla!

0.- La sintaxis de C++ apesta y la de C# es más limpia.

Bueno, al menos con C# podemos usar expresiones compactas cuando necesitamos una sóla línea de código, como al momento de filtrar o comparar en los ejemplos anteriores. Con C++ nunca es el caso. Pero bueno, al final no debería sorprendernos: la sintaxis de C++ suele ser más complicada aunque en ocasiones menos verbosa. C# por su parte fue diseñado para ser un lenguaje much más claro.

1.- Los lambdas en C# siempre se traducen a un delegado, mientras que los lambdas en C++ se traducen a un tipo de dato indefinido.

Esto, por supuesto, no quiere decir que en C++ los lambdas no tengan tipo: claro que lo tienen, tanto que hasta podemos usar typeid de esta forma:

auto func = []() { };
const type_info& type = typeid(func);
cout << type.raw_name() << endl;

/* en mi compilador, lo anterior imprime:

.?AV@?A0xe4d096c9@@
Press any key to continue . . .

*/

La diferencia radica en que en C++ nunca sabremos bien el tipo de dato del lambda, mientras que en C# sí lo sabemos. De hecho, en C# el compilador necesita alguna forma de inferir el tipo de dato, es decir, necesita inferir el delegado en cuestión.

2.- En C++ los lambdas pueden asignarse a variables con tipo de dato implícito, mientras que en C# no puede hacerse.

Esto es un corolario del punto anterior. En efecto, mientras que en C++ podemos inicializar un lambda hacia una variable con el modificador “auto”, hacer algo similar en C# con la palabra “var” nos manda error de compilación.

// C++ - OK
auto func = []() { };
// C# - error CS0815 Cannot assign lambda 
// expression to an implicitly-typed local variable
var func = () => { };

Como decíamos: en C++, el lambda tiene un tipo anónimo y por eso “auto” funciona, mientras que en C# necesariamente tiene que mapearse a un delegado: es por ello que el lambda necesita conocer de antemano el tipo del delegado.

3.- En C# todas las variables externas están disponibles en automático, y en C++ hay que hacerlo explícito.

En efecto, hemos visto cómo en C# podemos referenciar cualquier variable externa y listo, mientras que en C++ tenemos que, o bien nombrarlas explícitamente, o bien especificar que queremos que todo se pase por referencia (con el “[&]”).

4.- En C++ podemos controlar cómo pasar las variables externas.

Como ya decíamos, podemos especificar si una variable externa se pasa por valor o por referencia, o incluso pasar una por valor y otra por referencia.

5.- En ambos casos, las librerías de ambos lenguajes permiten usar lambdas para realizar filtros y acciones sobre datos.

Vimos varios ejemplos, como List<T>.Find o List<T>.Sort. Por el lado de C++, podemos usar std::for_each para ejecutar una acción (el lambda) sobre algún iterador. Por ejemplo:

list<int> l;
for (int i = 1; i <= 10; i++)
	l.push_back(i);

for_each(l.begin(), l.end(), [](int i) { cout << i*i << endl; });

 

Por último…

Bueno, pues eso ha sido todo por hoy. Estaría interesante que pudiésemos seguir analizando los lambdas, así que si quieres comentar algo, no dudes en dejar tu mensaje. ¡Incluso, me gustaría que buscásemos más similitudes y diferencias que las aquí mencionadas!

¡Nos vemos hasta la próxima!

Categorías:.NET Framework, Apunte, C#, C++ Etiquetas: , , ,

Filtrando colecciones con functores y lambdas


La librería estándar de C++ nos provee varias clases que nos permiten almacenar datos. Por ejemplo, std::list para listas, std::map para diccionarios, std::vector para elementos contíguos, etcétera. Todas estas clases de colecciones, también llamadas contenedores, están diseñadas para que su contenido pueda ser accedido mediante clases que enumeran cada elemento. A éstas se les llama iteradores. Los métodos begin y end nos proveen el primer y último iterador. Así, cualquier contenedor puede recorrerse de manera similar al siguiente ejemplo:

list<int> nums;
...

for (list<int>::iterator i = nums.begin(); i != nums.end(); ++i)
{
    cout << *i << endl;
}

O también podemos usar la nueva palabra reservada "auto":

list<int> nums;
...

for (auto i = nums.begin(); i != nums.end(); ++i)
{
    cout << *i << endl;
}

Por otra parte, recorrer un contenedor suele ser tan común que la librería estándar nos propone una función que toma tres parámetros: el iterador de inicio, el iterador de fin, y como tercer parámetro “algo” que tome un parámetro y ejecute una acción. Digo “algo” porque dado que es una plantilla, puede ser una función o, mejor aún, un functor. Un functor, antes que preguntes, es una clase que sobreescribe el operador () de tal suerte que puede invocarse como si fuera una función.

Para ejemplificar estos conceptos, me gustaría antes proponer una clase. Supongamos que vamos a trabajar con empleados. Creamos una clase similar a ésta.

class employee
{
    public:
        enum sex { sexMale, sexFemale };

        employee()
        {
            _age = 0;
            _gender = sexMale;
        }

        employee(const employee& copy)
        {
            _name = copy._name;
            _age = copy._age;
            _gender = copy._gender;
        }

        employee(wstring name, sex gender, int age)
        {
            _name = name;
            _age = age;
            _gender = gender;
        }

        wstring _name;
        sex _gender;
        int _age;
        
        bool operator== (const employee& employee)
        {
            return _name == employee._name;
        }

        bool operator< (const employee& employee)
        {
            return _name < employee._name;
        }
};

Ahora, en nuestra función main, supongamos que creamos un contenedor de esta forma.

employee emp1(L"Fernando", employee::sexMale, 28);
employee emp2(L"Catalina", employee::sexFemale, 31);
employee emp3(L"Moisés", employee::sexMale, 31);
employee emp4(L"Emmanuel", employee::sexMale, 27);
employee emp5(L"Paula", employee::sexFemale, 18);
employee emp6(L"Pedro", employee::sexMale, 24);

list<employee> employees;
employees.push_back(emp1);
employees.push_back(emp2);
employees.push_back(emp3);
employees.push_back(emp4);
employees.push_back(emp5);
employees.push_back(emp6);

Bueno bueno. Luego entonces, decíamos que podemos usar functores. Por ejemplo, creemos uno para imprimir todo el contenido de la lista.

class print_employee
{
    public:
        print_employee()
        {
            _print_all = false;
        }

        print_employee(bool print_all)
        {
            _print_all = print_all;
        }

        void operator()(const employee& employee)
        {
            wcout << employee._name << endl;
            if (_print_all)
            {
                wcout << L"Gender: "  
                    << (employee._gender == employee::sexMale ? L"Male" : L"Female") 
                    << endl;
                wcout << L"Age: " << employee._age << endl;
                wcout << L"*****\n" << endl;
            }
        }

        bool _print_all;
};

 

Ahora sí, usar la función for_each de esta forma.

print_employee print(false);
for_each(employees.begin(), employees.end(), print);

Como puedes ver, print_employee es una clase functor: ve cómo sobrecargamos el operador (). De hecho, podrías invocar directamente print(empleado), como si fuera una función; eso es justamente lo que hace for_each.

Ahora bien, una de las tareas que frecuentemente tendremos que hacer es filtrar un contenedor. Por ejemplo, podríamos querer filtrar los empleados por género, por edad o por nombre. Pues bien, lo que haremos será crear una clase con un functor. Esta clase tendrá un constructor que tomará como parámetro una referencia hacia el contenedor donde guardaremos los empleados que cumplan la condición del filtro. Veamos.

class filter_employee
{
    public:
        filter_employee(list& results)
            : _results(results)
        {
            _by_age = false;
            _min_age = 0;
            _max_age = 0;
            _by_name = false;
        }

        void by_age(int min, int max)
        {
            _by_age = true;
            _min_age = min;
            _max_age = max;
        }

        void by_name(const wstring& name)
        {
            _by_name = true;
            _name = name;
        }

        void operator()(const employee& value)
        {
            bool found = false;
            if (_by_age && !found)
            {
                found = value._age >= _min_age && 
                        value._age <= _max_age;
            }

            if (_by_name && !found)
            {
                found = value._name.find(_name) != wstring::npos;
            }

            if (found)
                _results.push_back(value);
        }

        list& _results;

    private:
        bool _by_age;
        int _min_age;
        int _max_age;
        bool _by_name;
        wstring _name;
};

Si invocamos by_age entonces el functor filtrará por un rango de edades. Si invocamos by_name, entonces buscará una subcadena de texto. Ahora, podemos usar la clase así, para filtrar empleados entre 28 y 42 años:

list results;

filter_employee filter(results);
filter.by_age(28, 42);
for_each(employees.begin(), employees.end(), filter);
for_each(results.begin(), results.end(), print);

Del código anterior, la primera línea inicializa una lista de empleados, donde el functor guardará los resultados obtenidos. La tercera línea inicializa el functor y la cuarta establece que se filtre por edad entre 28 y 42. La quinta ejecuta el filtrado: el functor será llamado para cada elemento y sólo los que cumplan la condición se almacenarán en la lista results. Por último, invocamos el functor de impresión que creamos anteriormente y pasamos los iteradores de results.

Y así, Charlie Brown, es como podemos usar functores para realizar filtros sobre colecciones. La verdad es que for_each nos ayuda bastante, aunque es cierto que crear una clase functor puede resultar tedioso. En los ejemplos anteriores, los functores son clases un poco más complicadas ya que permiten realizar ciertas configuraciones (al menos con filter_employee). Habrá casos, por supuesto, en los que no valga la pena crear un functor. Por ejemplo, print_employee sale sobrando. Para estas situaciones podemos usar lambdas.

Los lambdas son funciones anónimas, introducidas en C++ a partir del estándar C++11 (es decir, el del 2011). Aunque son recientes, afortunadamente Visual C++ 2010 ya las implementa, por lo que podemos hacer uso de ellas (obvio, si usas VC++; aunque supongo que otros compiladores de C++ ya habrán implementado los lambdas).

El siguiente código muestra un lambda para imprimir el empleado, así como su uso junto con for_each.

auto print_func = [](const employee& value) { wcout << value._name << endl; };

for_each(employees.begin(), employees.end(), print_func);

Como puedes ver, print_func es una función lambda. Los corchetes [] así lo indican, seguido de los parámetros (en este caso, una referencia constante a un employee), seguido del cuerpo de la función: en este caso, la impresión hacia la consola del nombre. Por supuesto, los lambdas pueden ser más complejos: podemos crear una también para filtrar por nombre, como en el siguiente código.

auto filter_func = [&results](const employee& value) { 
    if (value._name.find(L"P") != wstring::npos)
        results.push_back(value);
};
auto print_func = [](const employee& value) { wcout << value._name << endl; };
for_each(employees.begin(), employees.end(), filter_func);
for_each(results.begin(), results.end(), print_func);

En la primera línea del código anterior declaramos nuestro lambda. Nota que en esta ocasión entre los corchetes hemos colocado “&results”. Recordamos que results es la lista donde almacenamos los resultados. Así, esa expresión significa: pasa una referencia hacia results como parámetro a la función lambda. Si no ponemos nada entre corchetes, no podemos hacer referencia hacia results. Si dejamos el ámperson nada más (i.e. [&](const employee& value)) entonces indicamos que todas las variables que se declaren fuera de la lambda sean asequibles por referencia. Si colocamos el nombre results sin el ámperson, entonces quiere decir que lo pasamos por valor.

Así, como puedes ver, podemos usar cualquiera de los dos enfoques para realizar filtrado sobre un contenedor. Por supuesto que hay otros, pero estos son los que mejor me han funcionado. Mi recomendación es que juegues un poco con el código anterior y practiques.

¡Suerte!

Categorías:Apunte, C++ Etiquetas: , ,

Argumentos variables


Una de las herencias de C que fue bien acogida en C++ es el hecho de que una función tenga argumentos variables. Es decir, que el número de parámetros pueda variar y la función pueda reaccionar a este hecho como es debido.

Veamos un poquito de teoría. Todas las funciones, métodos, etc., tienen una dirección en memoria. El tamaño depende de la firma y el tipo de dato de regreso. La firma es el número de parámetros. Así, int foo(int a, int b); guardaría sizeof(int) bytes para el valor de retorno, el doble para los parámetros y otro tanto para la propia dirección de la función. De esta forma, los parámetros se albergan de forma contínua en memoria, al igual que un array. Entonces, conociendo yo la dirección en memoria de el primer parámetro, es fácil obtener el segundo:

int foo(int a, int b)
{
    int* p1;
    int* p2;

    p1 = &a;
    p2 = p1 + sizeof(int);
    cout << "Valor del segundo parámetro: " << *p2 << endl;
}

Este hecho es lo que hace posible que una función pueda tener parámetros variables: basta con que obtenga la dirección en memoria de la primera función para que, conociendo el tipo de parámetros que me envían, pueda convertir los bytes en el tipo de dato correspondiente.

Ok, basta de teoría. La librería estándar de C++ contiene –heredada de C– una serie de funciones que automatizan la tarea de manejar direcciones de memoria, sumarlas, etc. Veamos una pequeña referencia del tipo de dato y las funciones mencionadas.

  • va_list. Este tipo de dato va a albergar el puntero a las direcciones de memoria que contienen los argumentos. Usualmente es un typedef de un char*, ya que en esencia su utilidad es apuntar adecuadamente al búfer.
  • va_start. Esta función inicializa nuestra variable de tipo va_list. Toma dos parámetros. El primero es nuestra variable de tipo va_list. El segundo es el último parámetro conocido. Lo que hace esta función (o macro) es lo antes mencionado, toma la dirección de memoria del último parámetro conocido y posiciona el búfer pasada ésta dirección.
  • va_arg. Nos devuelve el valor del siguiente parámetro variable. Toma dos parámetros, el primero es nuestra variable va_arg, y el segundo es el tipo de dato que vamos a convertir (es decir, si el parámetro es un int, un double, etc; esto, para mover correctamente el puntero va_list y hacer el cast correspondiente).
  • va_end. Una vez que terminamos, hay que emplear esta macro para indicar que hemos terminado. El único parámetro es nuestro puntero va_list.

Es importante recalcar que es necesario saber el número de parámetros que se van a intentar obtener, de alguna forma, así como su tipo de dato. Por ejemplo, printfobtiene esta información del número de % que encuentre, así como el tipo de dato especificado (%d, %c, %s, etc). La función va_arg se puede llamar tantas veces como se requiera, pero hay que tener en cuenta que si nos pasamos podemos provocar algún comportamiento indefinido.

Bueno, en vivo y a todo color, un ejemplo.

int Suma(int cuenta, ...)
{
    va_list args;
    int total;

    va_start(args, cuenta);
    total = 0;

    for (int i = 0; i < cuenta; i++)
    {
        total += va_args(args, int);
    }
    va_end(args);

    return total;
}

int main()
{
    cout << "Suma 1+3+5+7 = " << Suma(4, 1, 3, 5, 7) << endl;
    cout << "Suma 1+2+3+4+5+6+7+8+9+10 = " <<
        Suma(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) << endl;
    cout << "Suma 6+9+8+4+3+0 = " << Suma(6, 6, 9, 8, 4, 3, 0) 
        << endl;

    return EXIT_SUCCESS;
}

Ahora solo queda que juegues con esto y hagas funciones más complicadas.

Categorías:Apunte, C++, Independiente Etiquetas:

Polimorfismo genérico


Si ya tienes tiempo trabajando con programación orientada a objetos, habrás notado cuán útil es el polimorfismo. Un ejemplo muy útil de esto es el patrón de diseño de la estrategia. Pero hay muchos otros ejemplos. Gracias a esto podemos cambiar comportamientos, y bueno, todo lo chido que tenemos con el polimorfismo.

Ahora bien, también tenemos una forma de hacer una especie de polimorfismo genérico, que me gustaría comentar dado que algunas librerías (como ATL o WTL) lo emplean. Consideremos el siguiente ejemplo:

template<class T>
class Base
{
public: 
    void Foo() 
    {
        T* pT = static_cast<T*>(this);
        pT->Imprimir();
    }
 
    void Imprimir() { cout << "Base::Imprimir"; }
};
 
class Derivada1 : public Base<Derivada1>
{
    // no hay nada aquí
};
 
class Derivada2: public Base<Derivada2>
{
    void Imprimir() { cout << "Derivada2::Imprimir"; }
};
 
int main()
{
    Derivada1 d1;
    Derivada2 d2;
 
    d1.Foo();    // imprime: "Base::Imprimir
    d2.Foo();    // imrpime: "Derivada2::Imprimir
 
    return 0;
}

Interesante, ¿no? Quizás hasta desconcertante. La clase Base toma un parámetro de plantilla que, además, espera sea alguna derivada de la misma Base. Lo interesante se presenta en la función Foo. Marcado en negritas, está la siguiente llamada: T* pT = static_cast<T*>(this);. ¿Qué quiere decir? Que vamos a convertir nuestro puntero (this) a una de las clases derivadas. Esto es correcto, toda vez que estamos pensando que el parámetro de plantilla sea una derivada de Base. Entonces, al hacer un static_cast hacia el parámetro de plantilla, e invoca a la función Imprimir de la misma; dado que no es virtual, no se invocará la implementación de la clase derivada, sino la implementación del parámetro de plantilla. Por eso es necesario hacer el static_cast. Así, al convertir a Derivada1, como ésta no tiene una implementación de Imprimir queoculte al Imprimir de Base, entonces se manda llamar a la versión de ésta. Sin embargo, Derivada2 sí implementaImprimir, y por lo tanto, oculta la de Base, y por ende es ésta la versión que es llamada.

Es interesante esta técnica. Tiene algunas ventajas, como el hecho de salvar memoria dado que no hay necesidad de vtables (tablas virtuales), o que todas las llamadas se resuelven en tiempo de compilación, por lo que pueden ser (y son) optimizadas. Pero quizás lo más importante es que es polimorfismo, pero no hay necesidad de tener que declarar una función virtual. Esto es muy importante en diversos escenarios, cuando, digamos, tenemos una librería que implementa el método X y que nosotros quisiéramos sobreescribir: dicho método tendría que haber sido declarado como virtual. Si no lo fué, aún tenemos la posibilidad de emplear el polimorfismo genérico.

Esta es, quizás, la característica más importante de esto.

Categorías:Apunte, C++, Independiente Etiquetas: ,

Estrategias


Supongamos que tenemos un conjunto de datos en particular, y que para manipularlos contamos con diferentes algoritmos para llevarlo a cabo. Ahora supongamos que por motivos de la vida tenemos que seleccionar el algoritmo a emplear en tiempo de ejecución (digamos tras seleccionar alguna opción o bajo determinadas circunstancias).

Bajo este escenario, tenemos que existe el patrón de diseño "Estrategia" (Strategy design pattern), que precísamente se centra en resolver este problema. Es decir, su finalidad es proveer un mecanismo para definir una familia de algoritmos, encapsularlos como objetos y hacerlos intercambiables. El patrón estrategia permite variar los algoritmos independientemente de los clientes que los usen. Esto se logra al desacoplar el algoritmo de su huésped (es decir, el cliente que lo utilizará), separándolos en otra clase.

Las ventajas de hacer esto son varias. En primera instancia, si tienes diferentes comportamientos es más fácil llevar el rastro de éstos en clases separadas en lugar de que todo esté en un mismo método, separado por ifs y elses. Otra ventaja es que si posteriormente se requiere agregar (o eliminar o cambiar) un comportamiento (i.e. el algoritmo), bastará con crear (o modificar) una clase derivada y pasarla a la clase ejecutora, y listo. Que por cierto, cada "comportamiento" o "algoritmo" es llamado una "estrategia", de ahí el nombre. Otra ventaja, inherente, es que las "estrategias" no están acopladas con el "contexto".

¿Cuándo emplear este patrón? Un buen criterio es cuando tienes varios objetos que son esencialmente lo mismo, pero que solo difieren en su comportamiento. De esta forma, se reducen todos estos objetos diferentes a uno solo que usa varias estrategias. Este empleo también es una buena alternativa al subclaseo de un objeto para alcanzar diferentes comportamientos. Normalmente, la idea de subclasear un objeto se pretende cambiar su comportamiento, que en principio es estático (a diferencia de cuando se pretende cambiar lo que hace, en cuyo caso sería mejor derivar esa clase y a través del polimorfiso, redefinir los métodos necesarios). Con las estrategias, todo lo que se necesita es reemplazar el objeto con una diferente estrategia, y listo. Es decir, si en algún momento de la vida tienes una clase que subclasea a otra… bien, podrías ir pensando en hacer una refactorización e implementar el patrón estrategia.

Pero bueno, ya fue mucha teoría, veamos algo de código. En primera instancia, el modelo a emplear para implementar este patrón sería algo similar a lo que muestra este diagrama (el cual es un fusil de la Wikipedia).

strategypattern

La clase Context es en esencia el objeto que mandará llamar a la estrategia definida. Y lo hará a través de una referencia a la clase base que define la estrategia propia. Posteriormente, se derivan tantas clases como estrategias se necesiten y listo. La clase del contexto deberá, por supuesto, tener una referencia a la estrategia que deberá seguir, y ponerlo en el constructor sería lo más adecuado.

Pero antes de ver código, pongamos un escenario práctico. El primero que viene a mi menete, y que es sencillo de entender, es el siguiente. Supongamos que tenemos una clase que tiene como miembro un array de, er, lo que sea, digamos cadenas de texo. Y lo que queremos es determinar una forma de ordenar dicho array. Dependiendo de cuántos elementos tenga el array, querremos ordenarlo por el método del quick sort, el método de la burbuja y quizás el método de selección. Entonces tendríamos que nuestro objeto "contexto" sería uno que tuviera un array; la estrategia "base" debería tener un método "Sort" donde le pasamos como parámetro el array a ordenar, y finalmente derivaríamos tres clases: QuickSort, BubbleSort e InsertionSort.

Tonz codiguillo:

class SortStrategy
{
    public:
        SortStrategy() { }
        virtual ~SortStrategy() { }

        virtual void Sort(string* array, int size) = 0; // virtualmente pura
};

class QuickSortStrategy : public SortStrategy
{
    public:
        QuickSortStrategy() { }
        virtual ~QuickSortStrategy() { }

        virtual void Sort(string* array, int size)
        {
            QuickSort(array, size-1, 0);
        }

    private:
        void QuickSort(string* array, int top, int bottom)
        {
            int middle;
        
            if (top < bottom)
            {
                middle = Partition(array, top, bottom);
                QuickSort(array, top, middle); //sí, estamos empleando recursión
                QuickSort(array, middle+1, bottom); 
            }
        }

        int Partition(string* array, int top, int bottom)
        {
            string x = array[top];
            int i = top - 1;
            int j = bottom + 1;
            string temp;

            do
            {
                do { j--; } while ( x.compare(array[j]) > 0);
                do { i++; } while ( x.compare(array[i]) < 0);

                if (i < j)
                { 
                    temp = array[i];
                    array[i] = array[j];
                    array[j] = temp;
                }
            } while (i < j);    
     
            return j;           
        }
};

class BubbleSortStrategy : public SortStrategy
{
    public:
        BubbleSortStrategy() { }
        virtual ~BubbleSortStrategy() { }

        virtual void Sort(string* array, int size)
        {
            // implementamos el bubble sort
            int i;
            int j;
            string aux_elem;
            int movs;

            movs = 0;

            for (int i = 0; i < size - 1; i++)
            {
                for (j = 1; j < size; j++)
                {
                    if (array[j] < array[j-1])
                    {   
                        aux_elem = array[j];
                        array[j] = array[j-1];
                        array[j-1] = aux_elem;
                        movs++;
                    }
                }
            }
        }
};

class InsertionSortStrategy : public SortStrategy
{
    public:
        InsertionSortStrategy () { }
        virtual ~InsertionSortStrategy () { }

        virtual void Sort(string* array, int size)
        {
            // implementamos el insertion sort
            int i, j, min;
            string str;

            for (i = 1; i > size; i++)
            {
                min = i;
                for (j = i+1; j <= n; j++)
                {
                    if (array[j] < array[min])
                        min = j;
                }

                str = array[min];
                array[min] = i;
                array[i] = str;
            }
        }
};

class Names
{
    private:
        string* _names;
        int _size;
        SortStrategy* _strategy;

    public:
        Names(int size, SortStrategy* strategy) 
        { 
            _size = size; _names = new string[size]; 
            _strategy = strategy; 
        }

        ~Names() { delete [] _names; delete _strategy; }

        inline int GetSize() const { return _size; }
        string& GetAt(int index) { return _names[index]; }
        string& operator[] (int index) { return GetAt(index); }

        void PrintNames(ostream& stream)
        { 
            _strategy->Sort(_names, _size);
            for (int i = 0; i < _size; i++) 
                stream << i << _names[i] << endl;
        } 
};

Ahora solo tenemos que usar las clases. En el ejemplo, pediremos que nos diga el usuario si ordenar por quick sort, por burbuja o por inserción. En situaciones más reales, digamos que decidiríamos de acuerdo al número de elementos que vamos a ingresar, pero en aras de hacer la cosa más sencilla, lo haremos así.

int main()
{
    SortStrategy* strategy;
    int opt;

    cout << "Seleccione una opción" << endl;
    cout << "1. QuickSort" << endl;
    cout << "2. Método de la burbuja" << endl;
    cout << "3. Método de inserción" << endl;
    cin >> opt;

    switch (opt)
    {
        case 1:
            strategy = new QuickSortStrategy(); break;

        case 2:
            strategy = new BubbleSortStrategy(); break;

        case 3:
            strategy = new InsertionSortStrategy(); break;

        default:
            cout << "Opción inválida, se usará QuickSort." << endl;
            strategy = new QuickSort();
            break;
    }

    Names names = strategy; // jeje, constructor implícito llamado aquí
    names[0] = "Fernando";
    names[1] = "Catalina";
    names[2] = "Eduardo";
    names[3] = "Tania";
    names[4] = "Andrés";
    names.PrintNames(cout);

    return EXIT_SUCCESS;
}

En cualquier caso, el código anterior nos mostraría la siguiente salida:

Andrés
Catalina
Eduardo
Fernando
Tania

ejemplificando a la perfección lo que comentaba arriba, de que el comportamiento no cambia, sino simplemente la forma de llevarlo a cabo. Por cierto que, si eres avispado, te habrás dado cuenta de que una cosa buena de esto es que las clases de estrategia (en este caso, QuickSortStrategy, BubbleSortStrategy y InsertionStrategy) pueden ser empleadas en cualquier lugar que quieras. Esto es a lo que nos referíamos con el hecho de que no están acopladas. Entonces una clase de búsqueda cualquiera, siempre que siga un patrón determinado (en este caso, la implementación del método Sort) puede emplearse como estrategia de búsquedas.

Bueno, pues farewell. Espero que te sirva de algo todo esto, de verdad que es uno de los patrones de diseño más empleados.

Tschüss!

Categorías:Apunte, C++, Independiente Etiquetas: ,