After doing a ton of testing and writing this answer on how to initialize a struct to zero in C++ (note: the downvote on it was before my total rewrite of it), I can't understand why = {0}
doesn't set all members of the struct to zero!
If you do this:
struct data_t
{
int num1 = 100;
int num2 = -100;
int num3;
int num4 = 150;
};
data_t d3 = {0};
printf("d3.num1 = %i\nd3.num2 = %i\nd3.num3 = %i\nd3.num4 = %i\n\n",
d3.num1, d3.num2, d3.num3, d3.num4);
...the output is:
d3.num1 = 0
d3.num2 = -100
d3.num3 = 0
d3.num4 = 150
...although I expected the output to be this:
d3.num1 = 0
d3.num2 = 0
d3.num3 = 0
d3.num4 = 0
...which means that only the FIRST member was set to zero, and all the rest were set to their defaults.
I was always under the impression that initializing a struct in any of these 3 ways would zero-initialize it, but obviously I'm wrong!
data_t d{}
data_t d = {}
data_t d = {0}
My key takeaway from this answer is therefore this:
The big take-away here is that NONE of these:
data_t d{}
,data_t d = {}
, anddata_t d = {0}
, actually set all members of a struct to zero!
data_t d{}
sets all values to their defaults defined in the struct.data_t d = {}
also sets all values to their defaults.- And
data_t d = {0}
sets only the FIRST value to zero, and all other values to their defaults.
So, why doesn't initializing a C++ struct to = {0}
set all of its members to 0?
Note that my key take-aways above actually contradict this rather official-looking documentation I've been using for years (https://en.cppreference.com/w/cpp/language/zero_initialization), which says that T t = {} ;
and T {} ;
are both zero initializers, when in fact, according to my tests and take-away above, they are NOT.
So, why doesn't initializing a C++ struct to = {0} set all of its members to 0?
Because you are only providing one value, while the class has more than one member.
When you have T t{};
or T t = {}
what you are doing is called value initialization. In value initialization, if the object/member does not have a default constructor, or a default member initializer, then the compiler falls back to zero initializing the objec/member. So with
data_t d{}
the value of the members in order would be 100, -100, 0 ,150 and that 0
for num3
happens because it has no default and you did not provide a value in the {}
so the compiler falls back to zero initializing num3
. This is the same with data_t d = {}
. With data_t d = {0}
you provide the first element, so num1
is 0
, but then like the first two, all of the other members are initialized with their default value if they have one, or zero initialized if they don't, giving you 0, -100, 0, 150 for the member values.
This was a change that happened when C++11 was released and allowed for default member initializers.
If your data_t
was defined like
typedef struct
{
int num1;
int num2;
int num3;
int num4;
} data_t;
then data_t d{}
, data_t d = {}
, data_t d = {0}
would all leave you with a zero initialized class since there are no default member initializers and the only value you provide in you braced-init-list (the technical name for {...}
) is zero so all members become zero.
data_t d3 = {0}
is list-initialization syntax which, with aggregates such as data_t
, performs aggregate initialization: the provided 0
value is used to initialized the first member and the remaining members are initialized using their corresponding defaults, and if none exist, are value-initialized (emphasis mine, edited for C++14):
If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are initialized by their default member initializers, if provided in the class definition, and otherwise by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.
value-initialization means zero-initialization for non-class types. That is why the member num3
, which has no default value, gets the value 0
.
Note: this is not to be confused with default-initialization, which does not initialize non-class types at all. data_t d3;
would be default-initialization, and the member num3
would be left in an indeterminate state.
The important point to keep an eye out for is whether or not the object being initialized is an aggregate because the initialization rules are different for aggregates vs. classes with a constructor. In case of a constructor, non-class members without a default value will be default-initialized (i.e. left in an indeterminate state).
Some examples:
struct A { // an aggregate
int num1 = 100;
int num2 = -100;
int num3;
};
struct B { // not an aggregate
int num1 = 100;
int num2 = -100;
int num3;
B() {}
B(int) {}
};
int main() {
A a1; // default-initialization: a1 is {100, -100, ???}
A a2 = {}; // aggregate initialization: a2 is {100, -100, 0}
A a3 = { 1 }; // aggregate initialization: a3 is {1, -100, 0}
A a4 = { 1,2,3 }; // aggregate initialization: a4 is {1, 2, 3}
B b1; // default-initialization: b1 is {100, -100, ???}
B b2 = {}; // copy-list-initialization invoking B::B(): b2 is {100, -100, ???}
B b3 = { 1 }; // copy-list-initialization invoking B::B(int): b3 is {100, -100, ???}
B b4 = { 1,2,3 }; // error: no B constructor taking (int,int,int)
}
Note also that aggregate initialization rules predate C++11. See for example this related pre-C++11 question: What does {0} mean when initializing an object?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With