In the previous lesson (8.12 -- Struct definition and member selection), we talked about how to define structs access their members. In this lesson, we’ll continue our exploration of structs, including how to initialize them, and how to pass them to functions.
Initializing structs
When a struct is defined, if no initializer is provided, each member of the struct is initialized identically to how it would be if it were defined outside the struct without an initializer. This means that members of a fundamental data type (e.g. int or double) will not be initialized by default.
1 |
Employee joe; // will not initialize any of joe's members since they all have fundamental data types |
For the same reason it’s a good idea to initialize your variables, it’s a good idea to initialize your structs.
When we initialize a normal variable, we provide a single initialization value. Because a struct can have multiple members, we need a way to initialize more than one value at initialization time. Because structs are an aggregate, they use a form of initialization called aggregate initialization. Aggregate initialization works just like normal initialization, but it allows you to initialize your aggregate using a list of values.
Structs can be copy-list or direct-list initialized:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct Employee { short id; int age; double wage; }; int main() { Employee frank = { 1, 32, 60000.0 }; // copy-list initialization Employee joe { 2, 28, 45000.0 }; // direct-list initialization using braced list Employee robert ( 3, 45, 62500.0 ); // direct-list initialization (C++20) using parenthesized list return 0; } |
Note that each of our initializers is a list of comma separated values!
Each of these initialization forms does a memberwise initialization, which means each member in the struct is initialized in the order of declaration. Thus, Employee joe { 2, 28, 45000.0 };
initializes joe’s id
with value 2
, joe’s age
with value 28
, and joe’s wage
with value 45000.0
.
Best practice
Prefer the braced list form of struct initialization.
If a struct is initialized but the number of initialization values is fewer than the number of members, then all remaining members will generally be zero-initialized.
For advanced readers
If the member is a class type with a default constructor, the member will instead be initialized to whatever default value the constructor function decides
This means we can use an empty list to initialize all member of the struct.
1 |
Employee joe {}; // zero-initialize all members (except class type members) |
Const structs
Variables of a struct type can be const, and just like all const variables, they must be initialized.
1 2 3 4 5 6 7 8 9 10 11 12 |
struct Rectangle { double length; double width; }; int main() { const Rectangle unit { 1.0, 1.0 }; return 0; } |
Non-static member initialization
It’s also possible to give non-static struct members a default value. This is done by providing the default value in the struct definition:
1 2 3 4 5 |
struct Rectangle { double length{ 1.0 }; double width{ 1.0 }; }; |
Warning
In C++11, the non-static member initialization syntax is incompatible with list-initialization syntax. For example, in C++11, the following program won’t compile:
1 2 3 4 5 6 7 8 9 10 11 12 |
struct Rectangle { double length{ 1.0 }; // non-static member initialization double width{ 1.0 }; }; int main() { Rectangle x{ 2.0, 2.0 }; // list-initialization return 0; } |
That makes non-static member initialization pretty useless in C++11. Fortunately, this limitation was fixed in C++14.
If both a non-static member initializer and a list-initializer are provided, the list-initializer takes precedence. In the above example, Rectangle x
would be initialized with length
and width
2.0
.
Best practice
Default your non-static members to an appropriate value. That ensures that your struct members are initialized even if the struct variable definition doesn’t include initializers.
Key insight
Using default initializers (and other mechanisms that we’ll cover later), class types (which includes structs) can self-initialize to non-zero values.
Designated initializers
When initializing a struct from a list of values, the values in the list are applied to the members in order of declaration.
1 2 3 4 5 6 7 8 9 10 |
struct Foo { int a { }; int b { }; } int main() { Foo f { 1.0, 2.0 }; // a = 1.0, b = 2.0 } |
Now consider what would happen if you were to add a new member to your struct that is not the last member:
1 2 3 4 5 6 7 8 9 10 11 |
struct Foo { int a { }; int c { }; // just added int b { }; } int main() { Foo f { 1.0, 2.0 }; // now, a = 1.0, c = 2.0, b = 0.0 } |
Now all your initialization values have shifted, and worse, the compile may not detect this as an error (after all, the syntax is still valid).
To help avoid this, C++20 adds a new way to initialize struct members called designated initializers
. Designated initializers allow you to explicitly define which initialization values map to which members. The members must be initialized in the order in which they are declared in the struct, otherwise an error will result.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct Rectangle { double length{ 1.0 }; // non-static member initialization double width{ 1.0 }; }; int main() { Rectangle a { .length = 2.0, .width = 3.0 }; // ok Rectangle b { .width = 3.0, .length = 2.0 }; // error: initialization order does not match order of declaration in struct return 0; } |
Now if you add a new member to the middle of the struct declaration, your initialization order will not match the order of declaration, and the compiler will error.
If there are less designated initializers than members, the remaining members are initialized in the same way as they would be for normal struct initialization.
Designated initializers are nice because they provide some level of self-documentation and help ensure you don’t inadvertently mix up the order of your initialization values.
List-assignment
As shown in the prior lesson, we can assign values to members of structs individually:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
struct Employee { short id; int age; double wage; }; int main() { Employee joe; joe.id = 1; joe.age = 32; joe.wage = 60000.0; return 0; } |
This can be a pain, particularly for structs with many members. Similar to initializing a struct with a list of values, you can also assign values to structs using a list of values:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
struct Employee { short id; int age; double wage; }; int main() { Employee joe { 1, 32, 60000.0 }; joe = { 1, 33, 66000.0 }; // Joe got older and also got a wage increase return 0; } |
![]() |
![]() |
![]() |
Leave a Reply