miércoles, 4 de abril de 2012

Nos mudamos

Voy a mudar este blog a http://davidcapello.com/blog/ de a poco.
Ya no habrá más posts nuevos aquí.

Los nuevos posts relacionados a C++ aparecerán bajo la categoría cpp, se pueden filtrar si la URL contiene /cpp/ (feed con posts de C++ usando Yahoo Pipes).

Otra mejor forma es directamente seguirnos en nuestra página de Google+ donde voy a estar publicando enlaces a los posts.

viernes, 24 de febrero de 2012

iosfwd

La librería estándar de C++ ofrece un archivo de cabecera de forward declarations de las clases de entrada y salida a flujos de bytes (iostreams):
#include <iosfwd>
De esta forma podemos utilizar los tipos ostream, istream, etc. sin necesidad de traer la definición de todas las clases de entrada y salida. Generalmente deberíamos utilizar el <iosfwd> en nuestro .h cuando no hagamos un mayor uso que sólo referenciarlas. Un ejemplo:
#ifndef EMPLEADO_IO_H
#define EMPLEADO_IO_H

#include <iosfwd> // forward declaration de ostream

class Empleado;    // forward declaration de nuestro tipo

// Aquí no necesitamos conocer el tamaño ni de Empleado,
// ni de ostream, podemos compilar sólo con sus forward
// declarations.
void write_empleado_en_formato_binario(const Empleado&, ostream&);

#endif
Regla: Siempre intenta usar las forward declarations antes que incluir los archivos de cabecera que definen las clases, los cuales suelen mostrar detalles de implementación que son propensos a cambiar. Siguiendo esta regla mejoraremos el tiempo de compilación y la cantidad de archivos a compilar al realizar una modificación en un .h.

Para más información, ver la sección 27.3 Forward declarations [iostream.forward] del último borrador público del estándar de C++

domingo, 12 de febrero de 2012

viernes, 23 de diciembre de 2011

Año nuevo

Bueno, este año fue poco productivo en este blog. El próximo no prometo mucho más, pero espero mejorar. Dejo un par de links que agregué en la sidebar: Los próximos años pueden llegar a ser los mejores años de C++. Con clang avanzando sobre gcc, es probable que aparezcan herramientas muy importantes para ayudar a programar en C++. Aquí les dejo un video:

¡Feliz año nuevo!

miércoles, 21 de septiembre de 2011

Código C++ moderno

Una excelente charla de Herb Sutter (en inglés) introduciendo algunas de las novedades del nuevo estándar C++11 y sobre cómo escribir código en C++ aprovechando todo el conocimiento acumulado en estos años (si nunca leíste C++ Coding Standards de Herb Sutter y Andrei Alexandrescu, compralo):
http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T
Los puntos tratados que no podes dejar pasar por alto:
  • El nuevo significado de la palabra auto para inferir tipos automáticamente.
  • La nueva sintaxis de lambdas para definir functors en línea y usarlos en algoritmos estándares (ej: for_each()).
  • Exception safety y heap lifetime con smart pointers, los cuales ya tenemos varios posts en este mismo blog sobre el concepto de smart pointers y RAII.
  • Nuevos contenedores de tipo hash table.
  • Move semantics que evita la creación y destrucción de objetos temporales innecesarias (ej: al retornar por valor).
  • Varios pequeños nuevos coding standards de C++11: funciones begin()/end(), override explícito, idioma pimpl con unique_ptr<>, etc.
En futuros posts voy a dedicar algo de tiempo en introducir estos temas y explicar por qué nacieron cada una de estas modificaciones en el nuevo estándar de C++.

martes, 8 de marzo de 2011

Cola de prioridades (STL priority_queue)

La cola de prioridades es una estructura de datos que se puede visualizar como una bolsa, donde podemos ir metiendo elementos uno atrás del otro, pero sólo los podemos retirar según un criterio de prioridad. Por ejemplo, en el caso de un ladrón obsesivo-compulsivo, podría robar un banco cargando bolsas de dinero sin ningún criterio, y al vaciarla podría retirar los billetes según su valor (primero los de $100, luego los de $50, etc.).

Otro ejemplo, imaginemos que tenemos un barco:
#include <queue>
#include <list>

using namespace std;

class Barco {
public:
  void subirPasajero(const Pasajero& pasajero);
  void abandonarBarco(list<Pasajero>& pasajerosEnOrden);

private:
  priority_queue<Pasajero> pasajeros_;
};

La función subirPasajero() debe agregar un pasajero al barco, y abandonarBarco() debe sacar todos los pasajeros del barco y colocarlos (por orden de prioridad) en la lista que se recibe como argumento:
void Barco::subirPasajero(const Pasajero& pasajero)
{
  pasajeros_.push(pasajero);
}

void Barco::abandonarBarco(list<Pasajero>& pasajerosEnOrden)
{
  while (!pasajeros_.empty()) {
    pasajerosEnOrden.push_back(pasajeros_.top());
    pasajeros_.pop();
  }
}

Tenga en cuenta que la función miembro pop() de las colas en C++ (queue, priority_queue, etc.) no retornan el valor extraído. Esto es así para evitar crear copias de los objetos dentro de la cola. De este modo la función top() devuelve una referencia al elemento mismo que se encuentra en la cola, y pop() lo remueve sin devolver nada. Así nos evitamos de crear copias temporales de la instancia removida.

Para poder usar la priority_queue sobre un tipo de dato propio (como Pasajero), debemos ofrecer una implementación del operator<() para comparar distintas instancias de nuestro tipo de dato (este operador se utiliza para saber qué elemento debe salir último de la cola, es decir, el menor elemento tiene menor prioridad):
enum Prioridad { Capitan, Hombre, Mujer, Ninio };

class Pasajero {
public:
  Pasajero(Prioridad p) : prioridad_(p) { }

  bool operator<(const Pasajero& otro) const {
    return prioridad_ < otro.prioridad_;
  }

  Prioridad prioridad() const { return prioridad_; }

private:
  Prioridad prioridad_;
};

De este modo, el capitán tiene prioridad 0 y es tomado en cuenta como el de menor prioridad para abandonar el barco. Los pasajeros que mayor prioridad tienen de abandonar el barco son las mujeres y los niños. En este caso podrían tener igual prioridad, no tendría mucho sentido salvar a todos los niños sin sus respectivas madres.

Ahora podemos crear un ejemplo:
#include <iostream>

using namespace std;

int main()
{
  Barco titanic;
  titanic.subirPasajero(Pasajero(Capitan));
  titanic.subirPasajero(Pasajero(Mujer));
  titanic.subirPasajero(Pasajero(Hombre));
  titanic.subirPasajero(Pasajero(Mujer));
  titanic.subirPasajero(Pasajero(Ninio));
  titanic.subirPasajero(Pasajero(Hombre));
  titanic.subirPasajero(Pasajero(Ninio));
  // ...

  list<Pasajero> pasajeros;
  titanic.abandonarBarco(pasajeros);
  
  for (list<Pasajero>::iterator it = pasajeros.begin(),
         end = pasajeros.end(); it != end; ++it) {
    cout << it->prioridad() << "\n";
  }
  
  return 0;
}
El anterior ejemplo imprime:
3
3
2
2
1
1
0
Lo que significa que, sin importar el orden en el cual agregamos los pasajeros, primero extraemos los niños, luego las mujeres, los hombres, y finalmente el capitán.

Más información:
http://www.cplusplus.com/reference/stl/priority_queue/
http://en.wikipedia.org/wiki/Priority_queue

sábado, 18 de septiembre de 2010

Iostream - Redireccionar clog para "loguear" en un archivo

La iostream es la librería de entrada y salida de C++. Existen algunos streams estándares de salida: std::cout, std::cerr y std::clog. El primero apunta a la consola, y los dos últimos... también. Las diferencias son estas:
  • cout apunta a la salida estándar STDOUT (texto de resultado esperado de un programa);
  • cerr y clog apuntan a STDERR (salida de errores y cualquier otra porquería).
Aunque a primera vista todo el texto se envía a la consola, uno puede redireccionar la salida a otros lados. Por ejemplo, imaginemos que tenemos el siguiente programa test.exe:
#include <iostream>

using namespace std;

int main()
{
  cout << "A\n";
  cerr << "B\n";
  clog << "C\n";
  return 0;
}
Al ejecutarlo, obtenemos por pantalla las tres líneas:
test.exe [ENTER]
A
B
C
Pero resulta interesante saber que podemos redireccionar el STDOUT a un archivo y el STDERR a otro. Ejemplo:
test.exe 1>stdout.txt 2>stderr.txt
¿Qué demonios es 1 y 2? Los archivos tienen un descriptor que los identifica, 1 es para la STDOUT, y 2 para STDERR. El signo mayor (>) significa que "quiero redireccionar toda la salida de texto que vaya para este descriptor a este archivo". En el anterior ejemplo logramos obtener dos archivos distintos, stdout.txt que contiene una línea (A), y stderr.txt que contiene dos líneas (B y C).

Generalmente, los logs van a un archivo, no a la pantalla. Aunque por defecto clog mande todo a STDERR, resulta útil redireccionar este stream a un archivo propio (por ejemplo, test.log). De esta forma, podemos hacer uso de clog para "loguear" todo lo que nuestro programa hace.

¿Cómo se redirecciona clog? Básicamente los streams de C++ tienen un streambuf asociado, y éste es el realmente encargado de leer y escribir datos (en la pantalla, en un archivo, en un string en memoria, etc.). Por lo tanto, si creamos un fstream y le colocamos su propio streambuf a clog, podemos usar clog como un "alias" del fstream original (clog va a estar compartiendo el mismo streambuf que el fstream). El código resultante es bastante sencillo:

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
  // Creamos un archivo de salida para logging.
  ofstream test_log;
  test_log.open("test.log");

  // Obtenemos el streambuf actual de clog (esto
  // lo usaremos luego para restaurar el streambuf
  // a su valor original, por si las moscas).
  streambuf* old_rdbuf = clog.rdbuf();

  // Reemplazamos el streambuf de clog con el del archivo.
  // Ahora ambos streams utilizarán el mismo streambuf (es
  // decir, escriben en el archivo test.log).
  clog.rdbuf(test_log.rdbuf());

  // Hacemos lo mismo que el ejemplo original.
  cout << "A\n";
  cerr << "B\n";
  clog << "C\n";
  
  // Restauramos el viejo streambuf de clog.
  clog.rdbuf(old_rdbuf);

  // Cerramos el archivo.
  test_log.close();
  return 0;
}
Y listo, ahora podemos hacer lo mismo que antes:
test.exe 1>stdout.txt 2>stderr.txt
Con lo cual obtenemos tres archivos:
  • stdout.txt con la línea A.
  • stderr.txt con la línea B.
  • test.log con la línea C.