13.9 — Default member initialization

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:

struct Something
{
    int x;       // no initialization value (bad)
    int y {};    // value-initialized by default
    int z { 2 }; // explicit default value
};

int main()
{
    Something s1; // s1.x is uninitialized, s1.y is 0, and s1.z is 2

    return 0;
}

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.

struct Something
{
    int x;       // no default initialization value (bad)
    int y {};    // value-initialized by default
    int z { 2 }; // explicit default value
};

int main()
{
    Something s2 { 5, 6, 7 }; // use explicit initializers for s2.x, s2.y, and s2.z (no default values are used)

    return 0;
}

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.

struct Something
{
    int x;       // no default initialization value (bad)
    int y {};    // value-initialized by default
    int z { 2 }; // explicit default value
};

int main()
{
    Something s3 {}; // value initialize s3.x, use default values for s3.y and s3.z

    return 0;
}

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:

struct Something
{
    int x;       // no default initialization value (bad)
    int y {};    // value-initialized by default
    int z { 2 }; // explicit default value
};

int main()
{
    Something s1;             // No initializer list: s1.x is uninitialized, s1.y and s1.z use defaults
    Something s2 { 5, 6, 7 }; // Explicit initializers: s2.x, s2.y, and s2.z use explicit values (no default values are used)
    Something s3 {};          // Missing initializers: s3.x is value initialized, s3.y and s3.z use defaults

    return 0;
}

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:

struct Fraction
{
	int numerator { }; // we should use { 0 } here, but for the sake of example we'll use value initialization instead
	int denominator { 1 };
};

int main()
{
	Fraction f1;          // f1.numerator value initialized to 0, f1.denominator defaulted to 1
	Fraction f2 {};       // f2.numerator value initialized to 0, f2.denominator defaulted to 1
	Fraction f3 { 6 };    // f3.numerator initialized to 6, f3.denominator defaulted to 1
	Fraction f4 { 5, 8 }; // f4.numerator initialized to 5, f4.denominator initialized to 8

	return 0;
}

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:

Fraction f1;          // f1.numerator value initialized to 0, f1.denominator defaulted to 1
Fraction f2 {};       // f2.numerator value initialized to 0, f2.denominator defaulted to 1

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.

guest
Your email address will not be displayed
Find a mistake? Leave a comment above!
Correction-related comments will be deleted after processing to help reduce clutter. Thanks for helping to make the site better for everyone!
Avatars from https://gravatar.com/ are connected to your provided email address.
Notify me about replies:  
40 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Wojtek Rzeplinski

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:

class A {
public:
    int x;
  A() {
    std::cout << "A default constructor" << std::endl;
    x = 15;
  }
};


struct S {
  int x;
  int y;
  A a;
};

int main() {
  S s;
  std::cout << s.x << std::endl;
    std::cout << s.y << std::endl;
    std::cout << s.a.x << std::endl;
}

which on my machine prints:

A default constructor
30558
-1113085360
15
jorge98

im disabling my adblock when im on this site because learning here is very fun

Tim

zip it up when your done pal

eman

Who hurt you

Kirill

Is it okay to initialize members using other members as default value? Like this:

#include <iostream>

struct Test
{
    int x{ 5 };
    int y{ x }; // using x as default value for y
};

int main()
{
    Test test{};
    return 0;
}

It seems to work fine, but I'm not sure, maybe it has some insidous and not-obvious consequences...

MOEGMA25
int numerator { }; // we should use { 0 } here, but for the sake of example we'll use value initialization instead

Why is that? Doesn't {}; already default initialize to 0?

Faizan

I don't think I'd ever do this but I need to ask any, with the following code

struct Foo
{
    int a{};
    int b{ 2 };
    int c{};
}

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

Foo foo{0, 2, 3);

or

Foo foo{};
foo.c = 3;
Swaminathan R
Something s3 {}; // value initialize s3.x, use default values for s3.y and s3.z

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.

Last edited 1 year ago by Swaminathan R
Asicx

"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 ?

Last edited 1 year ago by Asicx
D D

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.

  1. then the question arises, why write curly brackets when creating a structure type.
  2. there is a rule of plain old data, when we initialize the fields, we also have a constructor and now the structure is not a POD. Also it's not trivial: std::is_pod and std::is_trivial with {} become false
  3. A compiler even doesn't allow to use an uninitialized struct
struct Something
{
	int x;       // no default initialization value (bad)
	int y{};    // value-initialized by default
	int z{ 2 }; // explicit default value
	double s;
};
Something s2{ 5, 6, 7 };

We miss s2.s but it will be initialized. Does it matter to declare struct fields with {}?

struct Something2
{
	int x;
	int y;
	int z;
	double s;
};
Something s1{1,2};

Something2 is POD and trivial. s1.z and s2.s are now 0 and 0.0. Why do we make our structs more difficult?

Last edited 1 year ago by D D
Pete K

"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.

Raid

> s1.x is uninitialized

May I ask why s1.x is uninitialized instead of default initialization as mentioned in chapter 1.4?

Ther

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:

struct Something
{
    int x;
    int y{ 5 };
};

Something s1;

variable s1 is default initialized => variable s1.x is default initialized (=> unitialized). On the other hand, variable s1.y defaulted to value 5.

Last edited 1 year ago by Ther