In C++, we inherited this rule from C where primitive types like ints and pointers are uninitialized by default.
Before getting into it, essentially what I'm suggesting is first we add a syntax for explicitly leaving a variable uninitialized
int x = void; //I'm telling you x really is intended to start its life uninitialized
std::cout << x << std::endl; //undefined behavior
Next, we can suggest compilers warn if you don't provide an initializer for types that would otherwise default to be uninitialized.
int x; //warning no initializer! Use = void to create unintialized.
void* p; //warning no initializer! Use = void to create uninitialized. Suggest = nullptr for pointers.
vector<int> y; //Ok
int* z = nullptr; //Ok
int w = void; //Ok
char buf[4096] = void; //Ok
struct P { int x = 0; int y = void; int z = void; }; //Ok
P p = void; //Ok??
std::vector<int> v = void; //Error, cannot use = void for non-trivial types.
Note that for class types with constructors like vector, you can still go on with no initializer. The warning scheme is what maintains backwards compatibility. Migrating an old codebase to be warning free by just replacing all violations with = void is trivial via tooling nowadays.
There are some real benefits to uninitialized variables and some made up ones. But the bottom line is default uninitialized makes it very easy to write bugs, especially for beginners.
What are the supposed benefits of uninitialized variables?
1. Error detection.
Uninitialized memory access provides louder errors. A UMA can cause a crash with a core dump for ready debugging. They can easily be detected by modern tools like valgrind and address sanitizer. UMA's can also result in non-sense values that are outside of the range of whats expected, more easily corrupting the result of the entire application and making the problem easier to be noticed by an operator.
The last point is also dangerous. Imagine a trading system where an uninitialized memory read causes you to to buy several billion dollars in shares vs an incorrectly initialized variable at 0 simply causes you do not do anything.
2. Optimization opportunities.
This code does 2 copies.
char buf[4096] = {} //initialize to all zeroes
memcpy(buf, otherbuf, sizeof(buf));
If one removes the `= {}` bit, we don't initialize anything until memcpy is called. Saving useless work.
Some of these are suspicious at best, as modern compiles should be able to detect these scenarios and optimize out the redundant writes. Compilers have been eliminating dead stores for years. Still, we don't always have the best compilers especially in the embedded word. This kind of thing can be checked in the assembly.
In general, being able to take advantage of undefined behavior for error checking and optimization is a good thing. However I don't think its good how easily C++ lets you slide into UB land in subtle and non-obvious ways. Its important to have all of the levers available, they just need to be organized properly so the easy ones are easy and the hard ones are hard.
Another thing really bad about unitialized by default is that it creates an annoying and confusing rule about when you need to initialize variables.
When I write a class with members, like a trained monkey I always remember to explicitly initialize the primitives. Its annoying for me to do this, and more novice programmers often forget to do it.
struct Container {
int x = 0; //I have to remember to do this
Foo* p = nullptr; //Null pointer is almost always better than unitialized, as it ready produces a segfault and easy debugging.
std::unique_ptr<Foo> p2; //Almost the same as p, but default initialized to null.
std::vector<int> v;
std::array<int, 10> a = {{}}; //Yes you really need 2 braces on each side... Why does this have to be so different than std::vector?
};
We can't ever touch the backwards compatible rules about uninitialized variables. What we can do is add warnings and promote a safer more correct style for modern C++. Its still confusing that int x; and vector<int> x; are so different, but at least compilers can protect us from screwing this up until we're expert enough to understand when = void makes sense.