In C++, an uninitialized variable is a ticking time bomb 💣. Unlike languages like Java or C#, if you declare an int and don’t give it a value, C++ does not guarantee it will be 0.
Basically, it will contain binary garbage. That is, the value that was previously at that memory address… for example, -858993460… garbage.
Variable initialization is one of the most “messy” topics in C++. Because throughout the language’s history, different ways of doing the same thing have accumulated, each with its own nuances.
Let’s try to bring some order to this chaos. We’ll see how initialization has evolved from C++03 to the modern techniques of C++20.
The Classic Chaos (C++03)
In old C++ (before 2011), we had several ways to initialize variables, and the choice depended on whether we were dealing with a primitive type, a class, or an array.
Copy Initialization vs. Direct Initialization
Basically, we had two main groups:
// 1. Default initialization
int a1; // UNDEFINED value (Garbage). Danger!
// 2. Direct initialization (parentheses)
int a2(2); // Calls the constructor or assigns the value
int a3(0); // Value 0
// 3. Copy initialization (equals)
int a5 = 2; // Conceptually creates a temporary and copies it
Modern compilers optimize “copy initialization” to be identical to “direct initialization”, but semantically they were different.
The “Most Vexing Parse” Problem
Using parentheses for initialization brought one of the most famous and frustrating errors in C++. Watch out for this line:
int a4();
What the heck is a4?
- An integer variable initialized to 0?
- Or a function declaration that takes no parameters and returns an int?
The answer is the second one. C++ interprets this as a function. This is known as the Most Vexing Parse. To avoid it, we often had to resort to weird tricks or use copy initialization.
Arrays in C++03
Arrays had their own syntax using braces {} (which would later inspire the future of the language).
int a[3] = {1, 2, 3}; // Explicit size
int b[] = {1, 2, 3}; // Implicit size (deduced)
int d[3] = {1, 2}; // d[0]=1, d[1]=2, d[2]=0 (Fills the rest with zeros)
int e[4] = {0}; // Classic trick to set everything to 0
Uniform Initialization (C++11)
C++11 arrived and said:
Enough of this mess, you’ve got me up to the… Let’s use a common syntax for everything
Thus, Uniform Initialization (or Brace Initialization) was born. The idea is to use **braces {}** for everything.
// Direct list initialization (the recommended modern way)
int b1{2};
// Value initialization (Zero-initialization)
int b2{}; // It is guaranteed to be 0. Goodbye garbage!
Why use braces?
Besides uniformity, braces have two huge advantages:
- They avoid the Most Vexing Parse:
int b2{}is unequivocally a variable, never a function. - They prevent “Narrowing Conversion”: This is critical. If you try to put a value that doesn’t fit, the compiler gives an error.
int x = 3.5; // C++03: Compiles, truncates to 3, and you lose data silently
int y{3.5}; // C++11: COMPILER ERROR! Prevents data loss.
Arrays with Modern Syntax
Now we can omit the = if we want, making the syntax identical to that of classes or primitives.
int f[3]{}; // Array of 3 integers, all set to 0
int g[]{1, 2, 3}; // Array of 3 deduced integers
Initialization of Structs and Classes
When working with structs (or classes without complex constructors, known as Aggregates), initialization has also improved.
Suppose this structure:
struct Point {
int x;
int y;
};
- In C++03: We could use braces, but it was limited.
Point s1; // x, y contain garbage
Point s2 = {1, 2}; // x=1, y=2
- In C++11: We use uniform initialization.
Point p1{}; // x=0, y=0 (Safe)
Point p2{1, 2}; // x=1, y=2
Designated Initializers (C++20)
Here comes a feature we love and borrowed from C. Sometimes, if a structure has 10 fields, initializing it like Config c{1, true, 5, "hola"} is unreadable. What is the 1? What is the 5?
In C++20 we can use Designated Initializers:
struct Point3d { int x, y, z; };
// Much clearer and more readable
Point3d p{ .x = 10, .z = 30 };
// .y is automatically initialized to 0 because we didn't mention it
In C++, unlike C, order matters. You must initialize members in the same order they are declared in the struct. You cannot put .z before .x.
Initialization in Dynamic Memory
When we use pointers and new, the initialization rules are subtle but dangerous if ignored.
// 1. Uninitialized (Danger)
int* p1 = new int; // Allocates memory, but the value is GARBAGE
// 2. Zero-initialization (C++03)
int* p2 = new int(); // Allocates and sets to 0
// 3. Specific value (C++03)
int* p3 = new int(5); // Allocates and sets to 5
With the arrival of C++11, we can (should) use braces here too, especially for dynamic arrays.
// Dynamic array of 4 integers
int* arr1 = new int[4]; // GARBAGE values
int* arr2 = new int[4]{}; // All set to 0 (Recommended)
int* arr3 = new int[4]{1, 2};// 1, 2, 0, 0
Whenever you allocate an array with new, try to use {} at the end to ensure the memory is clean and doesn’t contain residual data from other applications.
Summary: What Should I Use?
- By default, use braces
{}. They protect you from dangerous conversions and the function declaration problem.
int counter{0};
std::string name{"Luis"};
std::vector<int> list{1, 2, 3};
- Use
=when you want to emphasize it’s a simple assignment or a clear literal value (stylistically it’s acceptable and very common).
auto value = 42; // With auto it looks very clean
