Archive

Archive for 24 febrero 2012

Beta de Visual Studio 11–29 de febrero


Pues bien, Soma Segar ha anunciado el primer beta de Visual Studio 11 y .NET 4.5 para el próximo 29. Y don Herb Sutter ya nos ha dado sus comentarios respecto a Visual C++. Resumiendo, algunas características interesantes:

  • Compatibilidad con el estándar C++ 11 prácticamente completado. Muchas cosas (como los lambdas) ya las tenemos desde VC++ 10, pero ahora sí estaremos completos. Esto es algo que se agradece de verdad.
  • Soporte para Windows 8 y Metro. Ya tendremos disponible C++/CX, el dialecto de Microsoft para trabajar con Metro. Cabe decir que éste es en realidad azúcar sintáctico, y que tras bambalinas el compilador genera código C++ y COM equivalente.
  • Windows Runtime Library. Una librería estilo ATL para que quienes no quieran usar C++/CX puedan interactuar con Metro sin necesidad de recurrir a COM directamente.
  • Soporte para paralelismo. Se liberan C++ AMP y el Parallel Patterns Library para trabajar con varios procesadores en pararlelo.
  • Mejoras en Intellisense y en el editor, algo de lo que sufrimos quienes usamos VC++; al parecer ¡ya nos van a poner al nivel de VC#!

También Visual C# trae cosas nuevas. Quizás lo mejor sea el soporte para programación asíncrona.

  • Soporte para realizar tareas asíncronas, incluídas en el lenguaje. El uso de “async” y “await” nos permite trabajar con tareas paralelas sin tener que meternos en multiárea. El colega Javier Torrecilla dio un muy buen webcast ayer mismo respecto a este tema.

Respecto al .NET Framework 4.5, algunas de als cosas interesantes que he encontrado son las siguientes.

  • Soporte en .NET para aplicaciones Metro. Obvio no podría quedarse atrás.
  • Mejoras en ASP.NET para soportar HTML 5 y CSS 3.0, soporte al protocolo WebSockets, lectura y escritura de las peticiones HTTP asíncronas.
  • En Windows Presentation Foundation ya tendremos un nuevo control: el Ribbon. Si bien es cierto que éste estaba disponible en Codeplex, ahora ya es parte integral del Framework.
  • Tras el fracaso que supuso quitar los flujos de trabajo de máquinas de estado en Windows Workflow Foundation, la nueva versión ya los volvió a implementar. ¡Enhorabuena! Esto ha hecho renacer mi interés por WWF.
  • Ah, y en la versión anterior de WWF se añadieron actividades que soportaban expresiones en VB, ahora soportan también expresiones en C#. ¡Uf!

También hay cosas para los desarrolladores de F#, muchas de ellas interesantes. Estas dos llamaron poderosamente mi atención.

  • Proveedores de tipos. Permiten generar tipos basados en datos estructurados, lo cual ayuda a acceder a fuentes de datos en bases de datos como SQL. Con esto, en mi opinión, F# da el gran paso de ser una herramienta académica a una herramienta de productividad en programas empresariales.
  • Expresiones de consulta, característica que implementa LINQ para F#. ¡Náaaaaaais!

¡Así que ya sabes, a descargar el beta!

Categorías:Noticias Etiquetas: , ,

Filtrando contenedores mediante boost::filter_iterator


Cuando creamos nuestro modelo de datos, a veces nos encontramos con que necesitamos crear clases que actúan como contenedores. En estos casos lo natural que expongamos una forma a través de la cual podamos acceder a los objetos contenidos. Pero la mayoría de las veces (a menos que se trate de un contenedor trivial) también querremos exponer una forma para filtrar el contenido por criterios definidos.

En el caso de que el contenedor sea trivial y sólo necesites acceder al contenido, nos basta entonces con usar alguno de los contenedores estándares que la librería estándar de C++ pone a nuestra disposición, como std::list o std::vector. Pero ¿y cuando queremos añadir filtros? Por supuesto que hay varias formas de hacerlo, pero eso lo veremos más adelante. Comencemos por armar nuestro ejemplo base.

Pensemos en una clase empleado que tiene algunos atributos: nombre, edad, salario y un puntero hacia el jefe (o nulo si no lo tiene); y un método que nos permita imprimir el nombre del empleado hacia la consola. Nothing too fancy:


class employee
{
    public:
        employee()
            : income(0.0), age(0), manager(nullptr)
        { }
        employee(const string& name, double income, int age, employee* manager)
            : name(name), income(income), age(age), manager(manager)
        { }
        employee(const employee& copy)
            : name(copy.name), income(copy.income), age(copy.age), 
              manager(copy.manager)
        { }

        string manager_name() const { 
            return manager != nullptr ? manager->name : "[Sin jefe]"; 
        }

        void print() const { cout << name << endl; }

        string name;
        double income;
        int age;
        employee* manager;
};

Nada del otro mundo. Ahora pensemos en cómo sería nuestra clase contenedora. La librería estándar de C++ nos provee los contenedores de la STL, como std::list o std::vector. Todos estos contenedores se basan en la idea de separar el acceso a los datos del algoritmo empleado para almacenarlos. En otras palabras, la forma en la que obtenemos los objetos no varía respecto de la forma en la que éstos se almacenan (en nodos, en bloques de memoria adyacentes, etc.).

La forma de acceder a los objetos se hace mediante iteradores. El concepto de un iterador es el de un puntero hacia el siguiente elemento de un contenedor (iterador de avance), hacia el siguiente y el anterior (iterador bidireccional), o hacia un elemento particular (iterador de acceso aleatorio). De esta forma, dado un elemento cualquiera dentro de la colección siempre será posible acceder al que le sigue, sin importar la forma en la que esté implementado el algoritmo interno.

Dado lo anterior, es una buena idea seguir el mismo patrón para nuestra clase contenedora. Aunque internamente usaremos un std::list para almacenar el contenido.



class catalog
{
    public:
        typedef list<employee> employee_list;
        typedef employee_list::size_type size_type;
        typedef employee_list::iterator iterator;
        typedef employee_list::const_iterator const_iterator;

    private:
        employee_list _employees;
        
    public:

        catalog() { }

        size_type size() const { return _employees.size(); }
        void clear() { _employees.clear(); }

        void add(const employee& emp) { _employees.push_back(emp); }
        void add(const string& name, double income, int age, employee* mgr) {
            add(employee(name, income, age, mgr));
        }
};

Bueno, sólo son algunos typedefs y algunos métodos comúnmente encontrados en este tipo de clases. Ahora bien, queremos exponer los elementos del catálogo, así que bien haríamos con añadir un método begin_all y end_all que nos devuelva el iterador inicial y el final.


        iterator begin_all() { return _employees.begin(); }
        iterator end() { return _employees.end(); }

Bueno, ahora sí. ¿Cómo le hacemos para añadir filtros? Por ejemplo, ¿cómo le hacemos si quisiéramos, de alguna forma, exponer a aquellos empleados cuyo nombre contiene alguna letra o cuya edad se encuentra en un rango determinado?

Una forma, por supuesto, sería crear un método similar a este:

...

void fill_by_age(list& lst, int min, int max)
{
    for (auto i = employees.begin(); i != employees.end(); ++i)
    {
        if (min <= age && i->age <= max)
            lst.push_back(*i);
    }
}
...

Aunque correcto, este código presenta ciertas deficiencias de diseño. Para empezar tenemos que pasar una referencia al contenedor donde se guardarán. Para seguir, tenemos que especificar el tipo de contenedor que usaremos (std::list). Si mañana quisiéramos cambiar de un list a un vector o a un map, rompería código ya escrito y habría que rehacerlo. Y por último, aquí estamos rompiendo la regla de separar implementación del acceso.

No hay mucho qué hacer para el primer problema. Pero para resolver el segundo, podríamos crear un método plantilla y que ahí se decida el tipo de contenedor. Y aun si obviáramos los problemas de estar manejando plantillas, no resolveríamos el tercer problema.

¿Qué podemos hacer? Bueno, una alternativa es regresar iteradores, similares al begin_all y end_all que hicimos hace rato. Pero en aquel momento simplemente pasamos los iteradores del contenedor interno (std::list). ¿Qué hacer ahora? ¿Crear nuestros propios iteradores, unos que hagan el filtro en automático?

Evidentemente es una opción, aunque es cierto que es una un tanto compleja y laboriosa. Y aquí el estándar de C++ nos deja sin opciones. Afortunadamente aquí es donde entra Boost. Esta biblioteca cuenta con una clase que nos ayuda, precísamente, a hacer esto que queremos. Estamos hablando de boost::filter_iterator<P, I>.

No voy a entrar en una discusión completa sobre esta clase (para eso, mejor revisar la documentación de Boost). Pero sí diré que es una clase que toma un predicado o functor (de tipo P), y un iterador de tipo I. La clase recorre el iterador, de inicio a fin, y para cada elemento ejecuta el predicado / functor en cuestión. Si éste regresa verdadero, filter_iterador añade el elemento a un iterador temporal. Si no, lo deja fuera. Luego, puede recorrerse este iterador temporal y todo lo que obtendremos serán los elementos filtrados.

¡Yay! ¡Suena como a algo que necesitamos! ¿Cómo lo implementamos?

Lo primero que necesitamos es un functor. Es decir, una clase con el operador () sobrecargado, y que tome como parámetro un objeto del contenedor, es decir, de tipo employee. Esta sobrecarga deberá regresar true para cuando queramos que el objeto pasado como parámetro sea incluido en el filtro. Así que podemos probar con un functor para filtrar la edad por un rango mínimo (ésto último pasados como parámetros al constructor del functor).

struct range_func
{
    range_func()
        : _min(0), _max(0)
    { }
    range_func(const range_func& copy)
        : _min(copy._min), _max(copy._max) 
    { }
    range_func(int min, int max)
        : _min(min), _max(max) 
    { }

    bool operator()(const employee& emp) const {
        return _min <= emp.age && emp.age <= _max;
    }

    int _min;
    int _max;
};

Sencillo, ¿no? Bueno, ahora lo que haremos es crear nuestro boost::filter_iterator para devolver un iterador de inicio y uno de fin. Para ello, usaremos la función boost::make_filter_iterator, en donde le pasaremos el functor ya creado y luego el iterador. Para el begin(), le pasamos begin() y end() de la lista, y para el end(), pasamos dos veces el end() de la lista. Algo así.

typedef filter_iterator range_filter_iterator;
...

range_filter_iterator begin_age_range(int min, int max)  {
	return begin_age_range(range_func(min, max));
}
		
range_filter_iterator begin_age_range(const range_func& func) {
	return make_filter_iterator<range_func>((func, 
        _employees.begin(), _employees.end());
}

range_filter_iterator end_age_range(int min, int max) {
    return end_age_range(range_func(min, max));
}

range_filter_iterator end_age_range(const range_func& func) {
    return make_filter_iterator<range_func>((func, 
        _employees.end(), _employees.end());
}

Un typedef al inicio para facilitar las cosas. Por lo demás, make_filter_iterator se encarga del resto. Ahora podemos usar nuestra clase de una forma similar a esta.

int main(int argc, char* argv[])
{
    catalog cat;
    employee fer("Fernando", 10000, 29, nullptr);
    cat.add(fer);
    cat.add("Gabriela", 13500, 26, &fer);
    employee jime("Jimena", 9500, 22, &fer);
    cat.add(jime);
    cat.add("Mario", 4300, 37, &jime);
    employee caty("Catalina", 15000, 31, nullptr);
    cat.add(caty);
    employee gis("Gisela", 14750, 30, &caty);
    cat.add(gis);
    cat.add("Omar", 12700, 30, &gis);

    auto func = [](const employee& emp) { 
        emp.print(); 
    };

    cout << "Edad entre 25 y 30" << endl;
    for_each(cat.begin_age_range(25, 30), cat.end_age_range(25, 30), func);
    cout << endl;

    ...
    return 0;
}

Como podemos ver, filter_iterator es una forma fácil de generar filtros y de paso resolver los tres problemas planteados hace un momento: no se expone el tipo del contenedor, y mantenemos separada la implementación.

Ya para terminar, dejo un programita de ejemplo completo: filtros por edad, nombre y jefe.



#include <iostream>
#include <string>
#include <list>
#include <functional>
#include <algorithm>
#include <boost/iterator/filter_iterator.hpp>

using std::string;
using std::cout;
using std::endl;
using std::list;
using std::function;
using std::for_each;
using std::shared_ptr;
using boost::filter_iterator;
using boost::make_filter_iterator;

class employee;

class employee
{
    public:
        employee()
            : income(0.0), age(0), manager(nullptr)
        { }
        employee(const string& name, double income, int age, employee* manager)
            : name(name), income(income), age(age), manager(manager)
        { }
        employee(const employee& copy)
            : name(copy.name), income(copy.income), age(copy.age), 
                manager(copy.manager)
        { }

        string manager_name() const { 
            return manager != nullptr ? manager->name : "[Sin jefe]"; 
        }

        void print() const { cout << name << endl; }

        string name;
        double income;
        int age;
        employee* manager;
};

struct range_func
{
    range_func()
        : _min(0), _max(0)
    { }
    range_func(const range_func& copy)
        : _min(copy._min), _max(copy._max) 
    { }
    range_func(int min, int max)
        : _min(min), _max(max) 
    { }

    bool operator()(const employee& emp) const {
        return _min <= emp.age && emp.age <= _max;
    }

    int _min;
    int _max;
};

struct substr_func
{
    substr_func() 
    { }
    substr_func(const string& str) 
        : _str(str)
    { }
    substr_func(const substr_func& copy)
        : _str(copy._str)
    { }

    bool operator()(const employee& emp) const {
        return emp.name.find(_str) != string::npos;
    }

    string _str;
};

struct mgr_func
{
    mgr_func()
        : _ptr(nullptr)
    { }
    mgr_func(employee* ptr)
        : _ptr(ptr)
    { }
    mgr_func(const mgr_func& copy)
        : _ptr(copy._ptr)
    { }

    bool operator()(const employee& emp) const {
        bool val = false;
        if (_ptr == nullptr)
            val = emp.manager == nullptr;
        else 
            val = emp.manager_name() == _ptr->name;
        return val;
    }

    employee* _ptr;
};


class catalog
{
    public:
        typedef list<employee> employee_list;
        typedef employee_list::size_type size_type;
        typedef employee_list::iterator iterator;
        typedef employee_list::const_iterator const_iterator;
        typedef filter_iterator<range_func, iterator> range_filter_iterator;
        typedef filter_iterator<substr_func, iterator> substr_filter_iterator;
        typedef filter_iterator<mgr_func, iterator> mgr_filter_iterator;

    private:
        employee_list _employees;
        
    public:

        catalog() { }

        size_type size() const { return _employees.size(); }
        void clear() { _employees.clear(); }

        void add(const employee& emp) { _employees.push_back(emp); }
        void add(const string& name, double income, int age, employee* mgr) {
            add(employee(name, income, age, mgr)); 
        }

        iterator begin_all() { return _employees.begin(); }
        iterator end() { return _employees.end(); }

        range_filter_iterator begin_age_range(int min, int max)  {
			return begin_age_range(range_func(min, max));
		}
		
		range_filter_iterator begin_age_range(const range_func& func) {
			return make_filter_iterator<range_func>(func, _employees.begin(), 
                _employees.end());
		}

        range_filter_iterator end_age_range(int min, int max) {
            return end_age_range(range_func(min, max));
        }

        range_filter_iterator end_age_range(const range_func& func) {
            return make_filter_iterator<range_func>(func, _employees.end(), 
                _employees.end());
        }

        substr_filter_iterator begin_substr(const string& value) {
            return begin_substr(substr_func(value));
        }

        substr_filter_iterator begin_substr(const substr_func& func) {
            return make_filter_iterator(func, _employees.begin(), 
                _employees.end());
        }

        substr_filter_iterator end_substr(const string& value) {
            return end_substr(substr_func(value));
        }

        substr_filter_iterator end_substr(const substr_func& func) {
            return make_filter_iterator(func, _employees.end(),
                 _employees.end());
        }

        mgr_filter_iterator begin_mgr(employee* mgr) {
            return begin_mgr(mgr_func(mgr));
        }

        mgr_filter_iterator begin_mgr(const mgr_func& func) {
            return make_filter_iterator(func, _employees.begin(), 
                _employees.end());
        }

        mgr_filter_iterator end_mgr(employee* mgr) {
            return end_mgr(mgr_func(mgr));
        }

        mgr_filter_iterator end_mgr(const mgr_func& func) {
            return make_filter_iterator(func, _employees.end(), 
                _employees.end());
        }
};

int main(int argc, char* argv[])
{
    catalog cat;
    employee fer("Fernando", 10000, 29, nullptr);
    cat.add(fer);
    cat.add("Gabriela", 13500, 26, &fer);
    employee jime("Jimena", 9500, 22, &fer);
    cat.add(jime);
    cat.add("Mario", 4300, 37, &jime);
    employee caty("Catalina", 15000, 31, nullptr);
    cat.add(caty);
    employee gis("Gisela", 14750, 30, &caty);
    cat.add(gis);
    cat.add("Omar", 12700, 30, &gis);

    auto func = [](const employee& emp) { 
        emp.print(); 
    };

    cout << "*** Todos los empleados *** " << endl;
    for_each(cat.begin_all(), cat.end(), func);
    cout << endl;

    cout << "***** POR EDAD *****" << endl;

    cout << "Edad entre 25 y 30" << endl;
    for_each(cat.begin_age_range(25, 30), cat.end_age_range(25, 30), func);
    cout << endl;

    cout << "Edad menor a 25" << endl;
    for_each(cat.begin_age_range(0, 25), cat.end_age_range(0, 25), func);
    cout << endl;

    cout << "Edad mayor a 30" << endl;
    for_each(cat.begin_age_range(30, 100), cat.end_age_range(30, 100), func);
    cout << endl;

    cout << "***** POR NOMBRE *****" << endl;

    cout << "Nombre que contenga 'na'" << endl;
    for_each(cat.begin_substr("na"), cat.end_substr("na"), func);
    cout << endl;

    cout << "Nombre que contenga 'ri'" << endl;
    for_each(cat.begin_substr("ri"), cat.end_substr("ri"), func);
    cout << endl;

    cout << "***** POR JEFE *****" << endl;

    cout << "Le reportan a Fernando" << endl;
    for_each(cat.begin_mgr(&fer), cat.end_mgr(&fer), func);
    cout << endl;

    cout << "Le reportan a Caty" << endl;
    for_each(cat.begin_mgr(&caty), cat.end_mgr(&caty), func);
    cout << endl;

    system("pause");
    return 0;
}


Y al ejecutarlo, tenemos este resultado:

*** Todos los empleados ***
Fernando
Gabriela
Jimena
Mario
Catalina
Gisela
Omar

***** POR EDAD *****
Edad entre 25 y 30
Fernando
Gabriela
Gisela
Omar

Edad menor a 25
Jimena

Edad mayor a 30
Mario
Catalina
Gisela
Omar

***** POR NOMBRE *****
Nombre que contenga 'na'
Fernando
Jimena
Catalina

Nombre que contenga 'ri'
Gabriela
Mario

***** POR JEFE *****
Le reportan a Fernando
Gabriela
Jimena

Le reportan a Caty
Gisela

Press any key to continue . . .

Ahora, intenta hacer algo similar sin functores, sino con lambdas… o mediante std::function. ¡Disfruta!

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

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!

Encuesta sobre Visual C++ 11, nuevas características y mis votos


En el blog del equipo Visual C++ ha aparecido una entrada en la que solicitan al mundo contestar una encuesta. Ésta hace referencia a aquellas características del nuevo estándar de C++ (llamado C++ 11) que queremos que el equipo de Visual C++ le de prioridad.

Como sabemos, algunas características de C++ 11 ya han sido incorporadas a Visual C++ 2010. Pero todavía faltan algunas otras, y Microsoft desea conocer nuestra opinión sobre a qué características darle prioridad.

Así que ya sabes: ¡participa! Aquí está la encuesta.

En mi caso, estas fueron las características que marqué como más urgentes.

1.- Delegating constructors. En C# podemos hacer esto:

class C
{
    string _param1;
    int _param2;

    C(string param1, int param2)
    {
        _param1 = param1;
        _param2 = param2;
    }

    C()
        : this(string.Empty, 0)
    {
    }
}

Esto rifa porque así creas un constructor genérico que puede ser invocado desde otros constructores. ¿No sería bueno contar con esto en nuestro Visual C++?

2.- Unicode literal strings. Para todos los que escribimos programas en otros lenguajes aparte del inglés, el mejorar el soporte para Unicode debería ser prioridad. Cierto, no sufrimos tanto como los chinos, árabes o hebreos, pero aún así debemos apoyar la causa: ¡por nuestro idioma!

3.- Use of char16_t y char32_t. Mismo razonamiento que el punto anterior, tratándose de tipos de dato para almacenar caracteres en Unicode (16 y 32 bits).

4.- Attributes. Similares a los atributos de C#:

// en C#
[WebMethod()] // este es un atributo
public void MyMethod()
{
}

El tener estos atributos en C++ nos ayudaría a olvidarnos de extensiones de compiladores como __declspec o __attribute__.

5.- Keyword exports. Aunque no es una opción, la puse en el punto 9, sobre comentarios adicionales.

La palabra reservada exports se utiliza cuando programamos clases plantillas. Hasta el momento, en Visual C++, tenemos que incorporar el cuerpo de nuestras funciones en la declaración de la clase. Intentarlo hacer por separado resulta en error de compilación.

Esta característica data del estándar C++ 98, si no me equivoco, pero Visual C++ nunca lo implementó. Y sé que no lo harán, menos ahora que el comité del estándar ha depreciado dicha palabra reservada (y la ha depreciado porque, salvo Comeau y Sun Studio, ningún otro compilador la implementó). Pero bueno, no se pierde nada con pedir.

Así que ya sabes, ¡vota por tus favoritas! ¡Participa!

Categorías:Noticias Etiquetas: ,

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: ,

Cómo abrir un formulario y cerrar el padre en VB.NET


Casi nunca escribo cosas de VB.NET, puesto que mi lenguaje preferido en .NET es C#. Sin embargo, hoy surgió una discusión en los foros de MSDN, y creo que es algo recurrente, así que mejor hago esta pequeña entrada.

El asunto es el siguiente. Supongamos que tengo Form1 y Form2, dos formas. Al iniciar la aplicación, inicio Form1 y quiero que al hacer clic a un botón dentro de ésta, me abra Form2.

La primera aproximacion es hacer un ShowDialog:

Public Class Form1

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) _
        Handles Button1.Click

        Dim form As New Form2

        form.ShowDialog(Me)
        
    End Sub
End Class

Esto muestra la segunda forma como modal de la primera. Si quisiéramos cerrar la forma original (i.e. Form1) podríamos hacer un close:

Public Class Form1

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) _
        Handles Button1.Click

        Dim form As New Form2

        form.ShowDialog(Me)
        Me.Close()
        
    End Sub
End Class

Pero no nos sirve, porque Close no se ejecutará hasta que ShowDialog termine, es decir, hastas que Form2 se cierre. Lo que podemos hacer es invocar a Show, en lugar de a ShowDialog.

Public Class Form1

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) _
        Handles Button1.Click

        Dim form As New Form2

        form.Show()
        Me.Close()
        
    End Sub
End Class

Así, Close se ejecuta inmediatamente después de Show. Sin embargo, si haces esto ahora nos damos cuenta que al hacer clic en el botón, sí se abre momentáneamente el Form2, pero ¡se cierra la aplicación!

Había escuchado muchos argumentos de que esto era por diseño, que la forma inicial siempre debía estar corriendo, etc. Me puse a picarle al código y encontré que en las propiedades del proyecto es posible especificar la forma en la que una aplicación VB termina: si queremos que termine cuando se cierra el formulario principal o si queremos que termine cuando se cierre el último formulario.

image

Una vez que cambiamos la opción a “Cerrar cuando se cierre el último formulario” ¡el código anterior funciona a la perfección!

Tras bambalinas pude ver que el diseñador de VB.NET 2010 genera una clase llamada MyApplication, la cual tiene una propiedad: ShutdownStyle, de tipo ShutdownMode, así que las propiedades del proyecto seguramente sólo cambian ese valor.

Y dado que la documentación data de .NET Framework 2.0, supongo que esta funcionalidad está disponible desde entonces (circa 2005).

Así que ya sabes: ¡haz la prueba y deja tus comentarios!

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: , , ,