When we define a struct (or class) type, we can provide a default initialization value for each member as part of the type definition. For members not marked as static
, this process is sometimes called non-static member initialization. The initialization value is called a default member initializer.
Related content
We cover static members and static member initialization in lesson 15.6 -- Static member variables.
Here’s an example:
In the above definition of Something
, x
has no default value, y
is value-initialized by default, and z
has the default value 2
. These default member initialization values will be used if the user doesn’t provide an explicit initialization value when instantiating an object of type Something
.
Our s1
object doesn’t have an initializer, so the members of s1
are initialized to their default values. s1.x
has no default initializer, so it remains uninitialized. s1.y
is value initialized by default, so it gets value 0
. And s1.z
is initialized with the value 2
.
Note that even though we haven’t provided an explicit initializer for s1.z
, it is initialized to a non-zero value because of the default member initializer provided.
Key insight
Using default member initializers (or other mechanisms that we’ll cover later), structs and classes can self-initialize even when no explicit initializers are provided!
For advanced readers
CTAD (which we cover in lesson 13.14 -- Class template argument deduction (CTAD) and deduction guides) cannot be used in non-static member initialization.
Explicit initialization values take precedence over default values
Explicit values in a list initializer always take precedence over default member initialization values.
In the above case, s2
has explicit initialization values for every member, so the default member initialization values are not used at all. This means s2.x
, s2.y
and s2.z
are initialized to the values 5
, 6
, and 7
respectively.
Missing initializers in an initializer list when default values exist
In the previous lesson (13.8 -- Struct aggregate initialization) we noted that if an aggregate is initialized but the number of initialization values is fewer than the number of members, then all remaining members will be value-initialized. However, if a default member initializer is provided for a given member, that default member initializer will be used instead.
In the above case, s3
is list initialized with an empty list, so all initializers are missing. This means that a default member initializer will be used if it exists, and value initialization will occur otherwise. Thus, s3.x
(which has no default member initializer) is value initialized to 0
, s3.y
is value initialized by default to 0
, and s3.z
is defaulted to value 2
.
Recapping the initialization possibilities
If an aggregate is defined with an initialization list:
- If an explicit initialization value exists, that explicit value is used.
- If an initializer is missing and a default member initializer exists, the default is used.
- If an initializer is missing and no default member initializer exists, value initialization occurs.
If an aggregate is defined with no initialization list:
- If a default member initializer exists, the default is used.
- If no default member initializer exists, the member remains uninitialized.
Members are always initialized in the order of declaration.
The following example recaps all possibilities:
The case we want to watch out for is s1.x
. Because s1
has no initializer list and x
has no default member initializer, s1.x
remains uninitialized (which is bad, since we should always initialize our variables).
Always provide default values for your members
To avoid the possibility of uninitialized members, simply ensure that each member has a default value (either an explicit default value, or an empty pair of braces). That way, our members will be initialized with some value regardless of whether we provide an initializer list or not.
Consider the following struct, which has all members defaulted:
In all cases, our members are initialized with values.
Best practice
Provide a default value for all members. This ensures that your members will be initialized even if the variable definition doesn’t include an initializer list.
Default initialization vs value initialization for aggregates
Revisiting two lines from the above example:
You’ll note that f1
is default initialized and f2
is value initialized, yet the results are the same (numerator
is initialized to 0
and denominator
is initialized to 1
). So which should we prefer?
The value initialization case (f2
) is safer, because it will ensure any members with no default values are value initialized (and although we should always provide default values for members, this protects against the case where one is missed).
Preferring value initialization has one more benefit -- it’s consistent with how we initialize objects of other types. Consistency helps prevent errors.
Best practice
For aggregates, prefer value initialization (with an empty braces initializer) to default initialization (with no braces).
That said, it’s not uncommon for programmers to use default initialization instead of value initialization for class types. This is partly for historic reasons (as value initialization wasn’t introduced until C++11), and partly because there is a particular case (for non-aggregates) where default initialization can be more efficient than value initialization (we cover this case in lesson 14.11 -- Default constructors and default arguments).
Therefore, we won’t be militant about enforcing use of value initialization for structs and classes in these tutorials, but we do strongly recommend it.
I have a problem with member initialization in classes.
> If an aggregate is defined with no initialization list:
> If a default member initializer exists, the default is used.
> If no default member initializer exists, the member remains uninitialized.
I believe that the last statement is over simplified.
Can we say that:
> If no default member initializer exists, the member is default initialized (which leaves it uninitialized in most cases).
From the future classes I know that default initialization calls default constructor of the class and I constructed the example to check if the constructor will be called:
which on my machine prints:
im disabling my adblock when im on this site because learning here is very fun
zip it up when your done pal
Who hurt you
Is it okay to initialize members using other members as default value? Like this:
It seems to work fine, but I'm not sure, maybe it has some insidous and not-obvious consequences...
Because members are initialized in the order in which they are defined, y will always be initialized after x, and thus this is well-defined.
Since these are initialized right at the point of definition, I'd say this is okay.
It's dangerous to do this in a constructor's member initializer list since the order of the members in the member initializer list may not match the order that the members are defined.
Why is that? Doesn't {}; already default initialize to 0?
Per the best practice in https://www.learncpp.com/cpp-tutorial/variable-assignment-and-initialization/, we prefer
0
over{}
when the value0
is meaningful and not just a placeholder.I don't think I'd ever do this but I need to ask any, with the following code
Lets say I want to create a Foo object, I want to keep a and b's default values (0 and 2) but use a different value for c. Assuming I'm using an earlier version than C++20 would I have to explicitly give values to a and b to give c a value if I want to use aggregate initialisation? If so what would be the best practice?
Assuming I want to assign c the value of 3
or
My opinion is that it depends on what you actually want. Use the former when you want
foo
to always have those values, even if the defaults are later changed. Use the latter if you wantfoo
to use the defaults, even if the defaults are later changed.Isn’t this behaviour counterintuitive and inconsistent with the usual purpose of {}? In all other cases,
eg: int a{};
we say zero initialize, but for class types, we say either value init. or zero init.No.
int a{}
is value initialization. In most cases, value initialization performs zero-initialization, but now you're encountering the cases where value initialization does NOT perform zero-initialization."This process is called non-static member initialization"
Can you tell us what "non-static" stands for in this context ? i mean why not just "member initialization". Is it related to the static kw, we saw earlier ?
In a future lesson, we'll cover static members, which have slightly different rules for initialization than non-static members. I added a related box with a link to that lesson.
Hello, Alex. please tell me why we initialize fields with curly brackets when creating a structure, because a variable of the type of this uninitialized structure cannot be used. If we do not initialize all the fields of the structure, then they are initialized with zeros by default anyway.
std::is_pod
andstd::is_trivial
with{}
become falseWe miss
s2.s
but it will be initialized. Does it matter to declare struct fields with{}
?Something2
is POD and trivial.s1.z
ands2.s
are now0
and0.0
. Why do we make our structs more difficult?We don't need our structs to be POD and trivial most of the time (as these mostly exist to ensure we can make structs that are C-compatible).
We initialize members with default values for at least two reasons:
IMO this is more useful than being a POD. Also, we make our structs more difficult when we impose more restrictions on them (e.g. to be a POD) rather than less!
"To avoid the possibility of uninitialized members, simply ensure that each member has a default value (either an explicit default value, or an empty pair of braces). That way, our members will be initialized with some value regardless of whether we provide an initializer list or not."
There should be a caveat that there are times, specifically in embedded real-time systems, where it might not be desirable to initialize fields of a struct. We have baselines that need to be in service > 25 years, some of which contain safety critical code which cannot easily be refactored and changed due to cost and lengthy recertification process, so while the example might seem esoteric, it's a real life example where you can't just change the design.
I have seen an example where a function f() was being called 2000 times per second and in that function a struct S was being instantiated as S s1; That struct had a small array in it and a few ints, none of which were initialized in the definition of the struct. Someone decided to add {} to all the fields in the definition of the struct, but the function f() was still creating the struct S as S s1;. The update to the definition of the struct then caused us to start missing deadlines in our real-time system. It was a pain in the neck to trace down that error during integration.The fields do eventually get assigned values in later processing. Yes, it probably is a bad design, but one that could not easily be changed given the constraints of the system.
Best practices are general advice designed to cover the vast majority of cases, when programming for modern devices that have ample memory and processing. Caveating esoteric exceptions to the rules isn't likely to benefit most readers. If you're working in a specialized or constrained environment, you're (hopefully) already aware of what practices should be disregarded for some specific reason...
> s1.x is uninitialized
May I ask why
s1.x
is uninitialized instead of default initialization as mentioned in chapter 1.4?I think it is needed to distinguish between variable X defaulted to value Y / default value Y for variable X was used and variable X was default initialized / default initialization of variable X occurred.
When you define variable like this
int x;
, default initialization occurs => variable holds a garbage value => variable is unitialized. So in this case:variable
s1
is default initialized => variables1.x
is default initialized (=> unitialized). On the other hand, variables1.y
defaulted to value5
.To show what happens in this case.