Default member initialization

When defining 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 is 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.

Here's an example:

```cpp struct Reading { int value; // no initialization value (bad) int status {}; // value-initialized by default int timestamp { 0 }; // explicit default value };

int main() { Reading sensor1; // sensor1.value is uninitialized, sensor1.status is 0, and sensor1.timestamp is 0

return 0;

}


In the above `Reading` definition, `value` has no default value, `status` is value-initialized by default, and `timestamp` has the default value `0`. These default member initialization values are used when the user doesn't provide an explicit initialization value during object instantiation.

Our `sensor1` object lacks an initializer, so its members are initialized to their default values. `sensor1.value` has no default initializer, so it remains uninitialized. `sensor1.status` is value-initialized by default, receiving value `0`. And `sensor1.timestamp` is initialized with value `0`.

Note that even though we didn't provide an explicit initializer for `sensor1.timestamp`, it initializes to a specific value because of the default member initializer provided.

<div class="info">
<strong>Key Concept</strong><br>
Using default member initializers (or other mechanisms we'll cover later), structs and classes can self-initialize even when no explicit initializers are provided.
</div>

**Advanced note:** CTAD cannot be used in non-static member initialization.



## Explicit initialization values take precedence over default values

Explicit values in a list initializer always override default member initialization values.

```cpp
struct Reading
{
    int value;       // no default initialization value (bad)
    int status {};   // value-initialized by default
    int timestamp { 0 }; // explicit default value
};

int main()
{
    Reading sensor2 { 42, 1, 1700000000 }; // use explicit initializers for sensor2.value, sensor2.status, and sensor2.timestamp (no defaults used)

    return 0;
}

In this case, sensor2 has explicit initialization values for every member, so default member initialization values aren't used. This means sensor2.value, sensor2.status and sensor2.timestamp are initialized to values 42, 1, and 1700000000 respectively.

Missing initializers in an initializer list when default values exist

In the previous lesson, we noted that if an aggregate is initialized with fewer values than members, remaining members are value-initialized. However, if a default member initializer is provided for a member, that default is used instead.

struct Reading
{
    int value;       // no default initialization value (bad)
    int status {};   // value-initialized by default
    int timestamp { 0 }; // explicit default value
};

int main()
{
    Reading sensor3 {}; // value initialize sensor3.value, use default values for sensor3.status and sensor3.timestamp

    return 0;
}

In this case, sensor3 is list-initialized with an empty list, so all initializers are missing. This means a default member initializer is used if it exists, and value initialization occurs otherwise. Thus, sensor3.value (which has no default member initializer) is value-initialized to 0, sensor3.status is value-initialized by default to 0, and sensor3.timestamp defaults to value 0.

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 declaration order.

The following example recaps all possibilities:

struct Reading
{
    int value;       // no default initialization value (bad)
    int status {};   // value-initialized by default
    int timestamp { 0 }; // explicit default value
};

int main()
{
    Reading r1;                     // No initializer list: r1.value is uninitialized, r1.status and r1.timestamp use defaults
    Reading r2 { 42, 1, 1700000000 }; // Explicit initializers: r2.value, r2.status, and r2.timestamp use explicit values (no defaults used)
    Reading r3 {};                  // Missing initializers: r3.value is value initialized, r3.status and r3.timestamp use defaults

    return 0;
}

The case requiring attention is r1.value. Because r1 has no initializer list and value has no default member initializer, r1.value remains uninitialized (which is problematic since we should always initialize variables).

Always provide default values for your members

To avoid uninitialized members, simply ensure each member has a default value (either an explicit default value, or an empty pair of braces). This way, our members are initialized with some value regardless of whether we provide an initializer list.

Consider the following struct with all members defaulted:

struct Coordinate
{
	int x { }; // we should use { 0 } here, but for demonstration we'll use value initialization instead
	int y { 1 };
};

int main()
{
	Coordinate c1;          // c1.x value initialized to 0, c1.y defaulted to 1
	Coordinate c2 {};       // c2.x value initialized to 0, c2.y defaulted to 1
	Coordinate c3 { 10 };   // c3.x initialized to 10, c3.y defaulted to 1
	Coordinate c4 { 5, 8 }; // c4.x initialized to 5, c4.y 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 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:

	Coordinate c1;          // c1.x value initialized to 0, c1.y defaulted to 1
	Coordinate c2 {};       // c2.x value initialized to 0, c2.y defaulted to 1

You'll note that c1 is default initialized and c2 is value initialized, yet the results are identical (x is initialized to 0 and y is initialized to 1). So which should we prefer?

The value initialization case (c2) is safer, because it ensures any members without default values are value-initialized (and although we should always provide default values for members, this protects against cases 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 historical (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.

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.

Summary

Default member initializers: Provide default initialization values for struct members directly in the type definition using syntax like int status {}; or int timestamp { 0 };. These values are used when no explicit initializer is provided for that member during object instantiation.

Explicit initializers take precedence: When a member has both a default member initializer and an explicit initializer in the initialization list, the explicit initializer is used. This allows you to override defaults on a per-object basis when needed.

Value initialization vs default initialization: When all members have default initializers, using value initialization (MyStruct obj {};) is safer than default initialization (MyStruct obj;). Value initialization ensures any members without defaults are still zero-initialized rather than left uninitialized.

Best practice - always provide defaults: Give every member a default value (either explicit like { 0 } or value initialization like {}). This ensures members are always initialized even if the object definition lacks an initializer list, preventing undefined behavior from uninitialized data.

Initialization priority order: If an aggregate is defined with an initialization list, members use explicit initializers first, then default member initializers if no explicit value exists, and finally value initialization if neither exists. Without an initialization list, members use their default initializer or remain uninitialized.

Consistency with other types: Preferring value initialization for aggregates (with empty braces) maintains consistency with how other types are initialized and protects against accidentally missing a default initializer for a member.

Default member initialization significantly reduces initialization errors and makes struct types more self-documenting by embedding sensible default values directly in the type definition.