Repaso a C++11: tipos automáticos

En este artículo vamos a repasar una de las características de C++11 que más le van a gustar a los programadores que les gusta escribir poco (la gran mayoría). Se trata de la asignación de tipos automática y la deducción de tipos.

Hasta ahora en el estándar de C++ la palabra reservada auto servía para indicar que una variable era local. Lo que sucede es que implícitamente todas las variables son declaradas auto a menos que se especifique como static o extern. Por esto mismo ningún programador usa nunca auto ya que es totalmente prescindible.

Con la llegada del nuevo estándar se le ha dado un nuevo uso a esta palabra que nos va evitar escribir bastante código y va a lograr que siempre elijamos el tipo más apropiado. Vamos a ver esto mejor con un par de ejemplos.

Cuando creamos un objeto dinámico tenemos un código parecido al siguiente:

Tipo *foo = new Tipo();

Como vemos tenemos que indicar dos veces de manera redundante el tipo del que tiene que ser la variable foo cuando el compilador ya sabe cual es el tipo correcto que tiene que asociar a dicha variable. De hecho, si usamos cualquier otro tipo nos dará un aviso.

Otro ejemplo sería cuando tenemos tipos demasiado largos que hacen que escribir un código en teoría simple se convierta en algo difícil de leer y mantener por parte del programador.

std::map > map;
for (std::map< std::string, std::list >::iterator it = map.begin(); it != map.end(); ++it)
{
    const std::string &clave = it->first;
    std::list &valor = it->second;
    for (std::list::iterator otIt = valor.begin(); otIt != valor.end(); ++it)
    {
        Otro *ob = *otIt;
    }
}

Es el caso de este ejemplo donde el uso de la STL y los iteradores nos dan tipos bastante largos. Es cierto que se podrían usar typedef para minimizar la escritura de tipos, pero no es lo más recomendado ya que obligas a terceras personas a adaptarse y entender a qué tipo se refieren los typedef definidos en el proyecto.

El último problema se refiere al uso del tipo correcto muchas veces usamos para cualquier tipo entero que nos devuelve una función el tipo int cuando muchas veces se nos devuelve un size_t o cualquier otro tipo obligando al compilador a hacer un casting de tipos con la posible pérdida de información o la estrechez del rango.

Un ejemplo de esto es el operador sizeof que devuelve un size_t (recordemos que como norma general se refiere a un entero sin signo) y los programadores solemos asociarlo a un entero con signo.

int a = 6;
int b = sizeof(a); // cuidado!!

Todos estos problemas se pueden solucionar de una manera bastante simple con el nuevo uso de la palabra reservada auto. Ahora auto nos sirve para que el compilador decida automáticamente el tipo más apropiado para una variable. Como puedes imaginar esto soluciona todos los problemas anteriores ya que nos ahorra tener que estar pensando en qué tipo concreto estamos usando.

Así pues en el primer ejemplo que vimos nos ahorraríamos la redundancia de tipos fácilmente con el uso de auto.

auto *x = new Tipo(args);

Del mismo modo nos ahorraríamos escribir tipos largos y tediosos que complican la lectura y seguimiento del código.

std::map > map;
for (auto it = map.begin(); it != map.end(); ++it)
{
    const auto &clave = it->first;
    auto &valor = it->second;
    for (auto otIt = valor.begin(); otIt != valor.end(); ++it)
    {
        auto *ob = *otIt;
    }
}

Creo que este código es mucho más legible que el que hay más arriba con todos los tipos de cada iterador. Se gana legibilidad, rapidez y sobre todo es menos propenso a errores al establecer los tipos el mismo compilador.

Por último ya sabemos que se usará el tipo correcto siempre, así que no tenemos porqué saber qué tipo concreto devuelve una determinada función evitando castings innecesarios.

int a = 6;
auto b = sizeof(a); // Tipo correcto

Reglas generales de uso de auto

Sin duda auto parece la panacea para los programadores más vagos y los más despistados, pero hay varias cosas que debemos tener en cuenta cuando lo usemos. Imagina un caso como el siguiente.

auto x=1, y=1.0;

En este ejemplo puede surgir la duda de que si a ambas variables se les asignará el mismo tipo o el más acorde. La regla dice que se debe evaluar cada tipo por separado, pero si al final todas las variables no son del mismo tipo daría como resultado un error de compilación. Por tanto algo como lo de arriba no se puede poner.

Por norma general se pueden dar los siguientes casos.

  • auto x = expr; El tipo que se deduce sería el resultado de la expresión, a menos que tenga un modificador const que se descartaría. Del mismo modo si el resultado de la expresión fuera un array o una función devolvería un puntero.

  • const auto x = expr; Es el mismo caso que el anterior, pero al resultado del tipo se le añadiría el modificador const. Recuerda que en el caso anterior era descartado.

  • auto &x = expr; El tipo sería una referencia al tipo devuelto por la expresión evaluada. Hay que tener en cuenta de que si se tratara de un R-Value generaría un error.

  • const auto &x = expr; Con el modificador const el caso anterior cambia y ya admitiría referencias a R-Values.

Deducción de tipos con decltype

Una de las deficiencias que C++ siempre ha tenido frente a otros lenguajes es la deducción de tipos. Hasta ahora no era posible saber de qué tipo era una variable concreta, como mucho podíamos calcular su tamaño en memoria con el operador sizeof.

Ahora con decltype es posible saber el tipo de cierta expresión. Las reglas son las siguientes.

  1. Si la expresión es una variable el tipo deducido será el tipo de la variable.

  2. Si la expresión es una función el tipo devuelto será el tipo de retorno de la función.

  3. Si se trata de un L-Value el resultado será una referencia al tipo de la expresión.

  4. En todos los demás casos el tipo deducido es el tipo de la expresión.

int i = 0;
int &r = i;
int &&rr = i;
decltype(i) a = i;
decltype(3) b = i;
decltype(r) c = i;
decltype(rr) d = 5;

Como vemos es útil para asignar tipos automáticamente, puede parecer parecido al uso de auto, pero no es igual ya que no se siguen las mismas reglas por lo que las siguientes expresiones no tienen porque ser equivalentes.

auto v = expr;
decltype(expr) v = expr;

Se le da al programador las dos opciones para que use siempre la más conveniente según el caso.

En este nuevo artículo de la serie de repaso a C++11 hemos tratado los tipos automáticos y la deducción automática de tipos que nos facilita la vida a los programadores haciendo que tengamos que escribir menos y por tanto haciendo que nuestro código sea más robusto y menos propenso a errores.

Ver todos los comentarios en https://www.genbeta.com

VER 0 Comentario

Portada de Genbeta