By the rules of value initialization. Value initialization occurs:
1,5) when a nameless temporary object is created with the initializer consisting of an empty pair of parentheses or braces (since C++11);
2,6) when an object with dynamic storage duration is created by a new-expression with the initializer consisting of an empty pair of parentheses or braces (since C++11);
3,7) when a non-static data member or a base class is initialized using a member initializer with an empty pair of parentheses or braces (since C++11);
4) when a named variable (automatic, static, or thread-local) is declared with the initializer consisting of a pair of braces.
Trivial example
struct A{
int i;
string s;
A(){};
};
A a{}
cout << a.i << endl // default initialized value
without explicitly declared constructor and left with defaulted default ctor // compiler generated one we get.
struct A{
int i;
string s;
};
A a{};
cout << a.i << endl // zero-initialized value
However using antoher struct.
struct A{
int i;
string s;
};
struct B{
A a;
int c;
};
B a{};
cout << a.a.i << endl // default initialized , even tho we did not , int struct B , declared A a{}.
The value of a.i is zero-initialized, even without using {} / () construct, which goes against rules ( if i am not mistaken ).
Using same logic on struct B:
struct A{
int i;
string s;
};
struct B{
A a;
int c;
};
B b;
cout << b.c << endl; // default inicialized
We get behavior according to rules.
The last example:
struct A
{
int i;
A() { }
};
struct B { A a; };
std::cout << B().a.i << endl;
B().a.i is also zero-initialized while we explicitly declared constructor and its not deleted.
Why are the these values getting zero-initialized? By rules stated here they should be default-initialized not zero-initialized.
Thanks for answers.
It's the difference between A
being an aggregate or not.
[dcl.init.aggr] (Emphasis mine)
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11),
So when A
has no declared constructors, saying A a{}
has the effect of aggregate initialization
which will construct each member with an empty initialization list:
If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from an empty initializer list
So you get int{}
and std::string{}
which will value-initialize the integer member to zero.
When you do provide a default constructor, the aggregate property is lost and the int
member remains uninitialized, so accessing it is considered undefined behavior.
To be specific:
This code is undefined behavior when accessing a.i
because you provided a user-defined constructor, so the int i
field remains uninitialized after construction:
struct A{
int i;
string s;
A(){};
};
A a{} ;
cout << a.i << endl;
And this code exhibits undefined behavior when accessing b.c
because you did not perform list initialization on B b
:
struct B{
A a;
int c;
};
B b;
cout << b.c << endl;
All the other code is okay, and will zero-initialize the integer fields. In the scenarios where you are using braces {}
, you are performing aggregate-initialization.
The last example is a little tricky because you're performing value-initialization. Since B
is an aggregate, it gets zero-initialized ([dcl.init]), in which:
each base-class subobject is zero-initialized
So again you're okay accessing the integer member of the A
subobject.