Las novedades de C++0x - (1º parte)

La historia del siguiente estándar de C++ bien podría llamarse la historia interminable.

C++ es un lenguaje de programación de propósito general, cuyo objetivo radica en añadir programación orientada a objetos a C.

Lejos de esto, decir que C++ es simplemente un C con objetos es quedarse muy lejos de la realidad. A lo largo de los años (y en sus dos estándares), se han ido añadiendo distintas funcionalidades al lenguaje que nada tienen que ver con la POO, pero que hacen más potente al lenguaje (templates, sobrecarga de operadores, manejo de excepciones, ...).

Y esta es su mayor virtud, y su mayor problema. La complejidad de C++ es muy alta debido al propio diseño del lenguaje y a todas las posibilidades que ofrece. Tanto que muchas empresas que lo usan deciden incluso obligar a usar solo un subconjunto del lenguaje para facilitar el desarrollo y sobretodo el mantenimiento de sus aplicaciones. Google, por ejemplo, descarta usar excepciones.

Pues la historia que nos atañe en esta serie de artículos es el desarrollo de C++0x, que es el próximo estándar de C++, que se empezó a desarrollar mucho tiempo ha, y que, como su propio nombre indica, se preveía que saldría durante la primera década de este siglo. Aunque no pudo ser así, hace apenas un par de meses (en Marzo de 2011) se anunció que el estándar ya estaba acabado, a la espera de aprobación final por parte de ISO, lo que significa que ya tenemos nueva versión de C++, llamada C++11, con muchísimas novedades que trataré de explicar.

En líneas generales esta nueva versión tiene como objetivos coger lo mejor de otros lenguajes (veremos características claramente influenciadas por C#, Java o Python), y simplificar el uso de este lenguaje (cosa que, a mi juicio, han conseguido muy malamente, la verdad).

Veamos las que más me han llamado la atención a mi (en un orden totalmente aleatorio).

Deduciendo tipos (auto)

Una de las primeras mejoras que han implementado en C++ (y que de hecho, ya tiene soporte actualmente en muchos compiladores) es la deducción de tipos.

C++ es un lenguaje tipado, y como tal, fuerza a declarar las variables y su tipo antes de poder usarlas. A partir de ahora, mediante la palabra auto, se nos permitirá no decir de que tipo es; siendo el compilador el encargado de averiguarlo a partir de la información del código fuente.

Esto, que lo más seguro es que provoque un montón de spaguetti code, puede llegar a ser muy útil en ciertas construcciones de C++ que son demasiado literales, y que solo complican la lectura del código. Como por ejemplo, la declaración de iteradores, que suele ser tan agradable a la vista como lo siguiente:

for (vector<vector<int>>::iterator i = v.begin();
            i != v.end(); ++i) {
     // Aquí hacemos algo interesante
}

A partir de ahora, y gracias a esta nueva funcionalidad, podremos hacerla de la siguiente manera

for (auto i = v.begin(); i != v.end(); ++i) {
     // Aquí hacemos algo interesante
}

Lo cual hace el código bastante más legible.

Como detalle adicional en el código presentado anteriormente, podemos ver otra de las nuevas funcionalidades de C++, que sin lugar a dudas provoca muchísimos dolores de cabeza actualmente.

Vemos que en el primer fragmento de código, no hemos tenido que separar los caracteres >. Actualmente es obligatorio, y lo que habría que escribir sería vector<vector<int> >. Esto ocurre debido a que los parsers actuales de C++ interpretan >> como el operador de desplazamiento, y no como la especialización del template que se está llevando a cabo.

A partir de ahora esto queda solucionado, y se podrán declarar variables de la forma vector<vector<int>> sin ningún problema.

Nuevos tipos enumerados (enum class)

Los enumerados de C y de C++ tienen dos problemas difíciles de resolver actualmente. El primero es que el nombre de los valores del enumerado se exportan al scope actual, lo que hace que dos enumerados totalmente distintos no puedan tener un valor con el mismo nombre.

Aparte, los enumerados son en el fondo enteros, lo que implica que dos enumerados totalmente distintos se puedan comparar entre ellos; o que, puedan ser comparables igualmente con un entero. Por último, el tipo subyacente que se usa para los enumerados no estaba definido por el estándar, por lo que era específica de la implementación de cada compilador.

Para resolver estos problemas se ha añadido un nuevo tipo de enumerado de tipado fuerte, que simula de cierta manera a los enumerados existentes en Java por ejemplo. A partir de ahora se nos permite especificar qué tipo queremos que tengan esos enumerados por debajo, y los distintos valores no se exportarán al scope actual, teniendo que prefijarlos con el nombre del enumerado para poder usarlos.

enum class Color : char {Amarillo, Azul, Negro};
enum class RGB {Rojo, Verde, Azul}; // No hay colisión con Azul

Color c = Color::Amarillo;
Color c2 = Azul; // FALLO: Azul no existe, sería Color::Azul

if (Color::Amarillo == 0) // FALLO: No comparable con un int
    printf("Blah!\n");

En este ejemplo podemos ver estos nuevos tipos de enumerados. El primero, Color, decidimos que tenga como tipo subyacente char, por lo que ocuparán 1B. Por su parte, el enumerado RGB no tiene tipo fijado, por lo que predeterminadamente será un int (usualmente 4B).

También podemos ver como no se genera un error aunque el valor Azul esté en ambos enumerados (esto provocaría un fallo en compilación con los enumerados normales), y por último, vemos como hay más restricciones a la hora de comparar los valores con otros tipos.

Bucle foreach (for (it : lista))

Cuando tenemos listados de elementos, una de las operaciones más básicas que podemos hacer es recorrer esa lista entera (con algún pretexto más o menos interesante). Para ello, lo más lógico actualmente es realizar un bucle for en el que dado el número de elementos que sabemos tiene la lista, empecemos a recorrerlos uno a uno.

Bien, la idea de esta nueva característica es emular a los bucles foreach de otros lenguajes como Java o C#. Es una extensión de los bucles for que facilita iterar por toda la lista, y que en cada paso te da una referencia al elemento que se está visitando en ese instante.

int numeros[] = {1,2,3,4,5};

// Incrementamos en +1 todos los elementos
for (auto it : numeros) // El tipo implícito es int&
    ++it;

El anterior es un ejemplo muy simple en el que vamos incrementando el valor de todos los números de la lista. Esta característica promete facilitar la lectura del código y hacerlo menos propenso a errores.

El nuevo puntero nulo (nullptr)

En C y C++ la constante NULL es representada por el valor numérico 0 (en C algo más complejo, ya que se castea a un void*). Esto genera problemas con las funciones o métodos sobrecargados, debido a que se dan comportamientos no esperados y muy difíciles de prever en el código.

void func(int n);
void func(char *n);

func(0); // Llamará a func(int)
func(NULL); // Llamará a func(int)

En este caso tenemos una función sobrecargada (puede recibir un entero o un carácter). El primer ejemplo vemos claramente que llamará a la primera función, ya que 0 es un entero normal y corriente. En cambio, el segundo ejemplo, que podríamos esperar que llamase a la segunda, no lo hace; y llama igualmente a la primera, o directamente falla al compilar por resultar ambigua (dependiendo de la implementación del compilador).

Para resolver este problema se ha añadido una constante llamada nullptr.

void func(int n);
void func(char *n);

char *c = nullptr;
char *c2 = NULL;

func(nullptr); // Llamará a func(char*)
bool t = (c == c2); // nullptr == NULL

Como podemos ver, esta constante soluciones los problemas generados en la sobrecarga y son totalmente compatibles con lo existente en C y C++.

Nuevas cadenas de carácteres

En C y C++ tenemos dos tipos de cadenas de caracteres. Los char, que representan caracteres de la tabla ASCII, y ocupan 1B; y los wchar_t, que se supone iban a facilitar el almacenamiento de caracteres Unicode. El problema es que el ancho no está definido en el estándar (en C por ejemplo wchar_t suele ser un alias de un int) y esto hace que no se puedan hacer implementaciones portables ya que no se asegura que tengan el suficiente ancho para alojar un carácter en UTF-8, UTF-16 u UTF-32.

Para resolver esta situación C++ crea dos nuevos tipos, char16_t y char32_t, que se usarán respectivamente para alojar caracteres de UTF-16 y UTF-32. De esta manera se asegura que estos tipos tengan un ancho suficiente (2B y 4B) para alojar los caracteres UNICODE.

const char *cad1 = u8"Esta cadena está en UTF-8";
const char16_t *cad2 = u"Esta cadena está en UTF-16";
const char32_t *cad3 = U"Esta cadena está en UTF-32";

Por último, y como podemos ver en el ejemplo anterior, podemos usar u8, u y U, para crear cadenas de caracteres en los tres sets dichos anteriormente.

Junto a estos tipos de datos se proporciona una nueva API con la que poder usarlos.

También se añade una nueva forma de escribir cadenas de caracteres en las que se toma literalmente cada carácter como es; es decir, no se escapan. Esto es muy útil a la hora de escribir expresiones regulares por ejemplo, donde cada vez que se escribe el carácter \, hay que escaparlo.

string c1 = "\\w\\\\\\w";
string c2 = R"[\w\\\w]";  // c1 y c2 son la misma cadena
string c3 = R"del(Buu!)del";  // La cadena es: Buu!

// La cadena es: Esta es una cadena raw y UTF-16
const char16_t *c4 = uR"*(Esta es una cadena raw y UTF-16)*";

Como se puede ver, para crear una raw string tenemos que empezar y terminar la cadena con el mismo delimitador (el cual puede ser cualquier cosa con como mucho 16 caracteres). La cadena resultante será la cadena completa, quitando estos delimitadores. Aparte, y como se puede ver en el último ejemplo, el uso es compatible con las cadenas Unicode que vimos anteriormente. Para marcar la cadena como raw string bastará con escribir una R previamente.

Y hasta aquí lo dejamos por hoy. En la segunda parte de la serie veremos el resto de novedades que nos proporcionará esta nueva versión de C++.

Artículos Relacionados

Comentarios