cpp-inicializacion-variables

Inicialización de variables en C++

  • 6 min

En C++, una variable no inicializada es una bomba de relojería 💣. A diferencia de lenguajes como Java o C#, si declaras un int y no le das valor, C++ no asegura que valga 0.

Basicamente tendrá basura binaria. Es decir, el valor que hubiera en esa dirección de memoria anteriormente… por ejemplo, -858993460… basura.

La inicialización de variables es, uno de los temas más “liosos” en C++. Porque a lo largo de la historia del lenguaje se han ido acumulando diferentes formas de hacer lo mismo, cada una con sus matices.

Vamos a intentar poner un poco de orden en este caos. Vamos a ver cómo ha evolucionado la inicialización desde C++03 hasta las modernas técnicas de C++20.

El caos clásico (C++03)

En el C++ antiguo (antes de 2011), teníamos varias formas de inicializar variables, y la elección dependía de si estábamos ante un tipo primitivo, una clase o un array.

Inicialización por copia vs directa

Básicamente, teníamos dos grandes grupos:

// 1. Inicialización por defecto
int a1;      // Valor INDEFINIDO (Basura). ¡Peligro! 

// 2. Inicialización directa (paréntesis)
int a2(2);   // Se llama al constructor o se asigna el valor
int a3(0);   // Valor 0

// 3. Inicialización por copia (igual)
int a5 = 2;  // Conceptualmente crea un temporal y lo copia

Copied!

Los compiladores modernos optimizan la “inicialización por copia” para que sea idéntica a la “directa”, semánticamente eran distintas.

El problema del “Most Vexing Parse”

El uso de paréntesis para inicializar trajo consigo uno de los errores más famosos y frustrantes de C++. Ojito a esta línea:

int a4(); 

Copied!

¿Qué narices es a4?

  • ¿Una variable entera inicializada a 0?
  • ¿O una declaración de función que no recibe parámetros y devuelve un int?

La respuesta es la segunda. C++ interpreta esto como una función. Esto se conoce como el Most Vexing Parse. Para evitarlo, a menudo teníamos que recurrir a trucos raros o usar la inicialización por copia.

Arrays en C++03

Los arrays tenían su propia sintaxis usando llaves {} (que luego inspiraría el futuro del lenguaje).

int a[3] = {1, 2, 3}; // Tamaño explícito
int b[]  = {1, 2, 3}; // Tamaño implícito (deducido)
int d[3] = {1, 2};    // d[0]=1, d[1]=2, d[2]=0 (Rellena con ceros el resto)
int e[4] = {0};       // Truco clásico para poner todo a 0

Copied!

Inicialización uniforme (C++11)

Llegó C++11 y dijo:

Basta de líos, me tenéis hasta los… Vamos a usar una sintaxis común para todo

Es posible que no lo dijeran exactamente así, pero algo parecido

Así nació la Inicialización Uniforme (o Brace Initialization). La idea es usar **llaves {}** para todo.

// Inicialización directa por lista (la recomendada modernamente)
int b1{2}; 

// Inicialización de valor (Zero-initialization)
int b2{};  // Se garantiza que es 0. ¡Adiós a la basura!

Copied!

¿Por qué usar llaves?

Además de la uniformidad, las llaves tienen dos ventajas brutales:

  1. Evitan el Most Vexing Parse: int b2{} es inequívocamente una variable, nunca una función.
  2. Previenen el “Narrowing Conversion”: Esto es crítico. Si intentas meter un valor que no cabe, el compilador da error.
int x = 3.5;  // C++03: Compila, trunca a 3 y pierdes datos silenciosamente 

int y{3.5};   // C++11: ¡ERROR DE COMPILACIÓN! Evita pérdida de datos.
Copied!

Arrays con sintaxis moderna

Ahora podemos omitir el = si queremos, haciendo la sintaxis idéntica a la de las clases o primitivos.

int f[3]{};       // Array de 3 enteros, todos a 0
int g[]{1, 2, 3}; // Array de 3 enteros deducidos

Copied!

Inicialización de Structs y Clases

Cuando trabajamos con structs (o clases sin constructores complejos, conocidas como Aggregates), la inicialización también ha mejorado.

Supongamos esta estructura:

struct Point { 
    int x; 
    int y; 
};

Copied!
  • En C++03: Podíamos usar llaves, pero era limitado.
Point s1;         // x, y tienen basura
Point s2 = {1, 2}; // x=1, y=2

Copied!
  • En C++11: Usamos la inicialización uniforme.
Point p1{};     // x=0, y=0 (Seguro)
Point p2{1, 2}; // x=1, y=2

Copied!

Inicializadores Designados (C++20)

Aquí viene una característica que nos encanta y que tomamos prestada de C. A veces, si una estructura tiene 10 campos, inicializarla así Config c{1, true, 5, "hola"} es ilegible. ¿Qué es el 1? ¿Qué es el 5?

En C++20 podemos usar los Designated Initializers:

struct Point3d { int x, y, z; };

// Mucho más claro y legible
Point3d p{ .x = 10, .z = 30 }; 
// .y se inicializa a 0 automáticamente porque no lo hemos mencionado

Copied!

En C++, a diferencia de C, el orden importa. Debes inicializar los miembros en el mismo orden en que están declarados en la struct. No puedes poner .z antes que .x.

Inicialización en memoria dinámica

Cuando usamos punteros y new, las reglas de inicialización son sutiles pero peligrosas si se ignoran.

// 1. Sin inicializar (Peligro)
int* p1 = new int;      // Reserva memoria, pero el valor es BASURA

// 2. Zero-initialization (C++03)
int* p2 = new int();    // Reserva y pone a 0

// 3. Valor específico (C++03)
int* p3 = new int(5);   // Reserva y pone a 5

Copied!

Con la llegada de C++11, podemos (debemos) usar llaves también aquí, especialmente para arrays dinámicos.

// Array dinámico de 4 enteros
int* arr1 = new int[4];      // Valores BASURA
int* arr2 = new int[4]{};    // Todo a 0 (Recomendado)
int* arr3 = new int[4]{1, 2};// 1, 2, 0, 0

Copied!

Siempre que reserves un array con new, intenta usar {} al final para asegurarte de que la memoria está limpia y no contiene datos residuales de otras aplicaciones.

Resumen: ¿Qué debo usar?

  1. Por defecto, usa llaves {}. Te protegen de conversiones peligrosas y del problema de la declaración de función.
int contador{0};
std::string nombre{"Luis"};
std::vector<int> lista{1, 2, 3};
Copied!
  1. Usa = cuando quieras enfatizar que es una asignación simple o un valor literal claro (estilísticamente es aceptable y muy común).
auto valor = 42; // Con auto queda muy limpio
Copied!