6/26/24, 11:26 AM 13.11 — Struct miscellany – Learn C++ LEARN C++ Skill up with our free tutorials 13.11 — Struct miscellany ALEX1 JUNE 8, 2024 Structs with program-defined members In C++, structs (and classes) can have members that are other program-defined types. There are two ways to do this. First, we can define one program-defined type (in the global scope) and then use it as a member of another program-defined type: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> COPY struct Employee { int id {}; int age {}; double wage {}; }; struct Company { int numberOfEmployees {}; Employee CEO {}; // Employee is a struct within the Company struct }; int main() { Company myCompany{ 7, { 1, 32, 55000.0 } }; // Nested initialization list to initialize Employee std::cout << myCompany.CEO.wage << '\n'; // print the CEO's wage return 0; } In the above case, we’ve defined an Employee struct, and then used that as a member in a Company struct. When we initialize our Company , we can also initialize our Employee by using a nested initialization list. And if we want to know what the CEO’s salary was, we simply use the member selection operator twice: myCompany.CEO.wage; Second, types can also be nested inside other types, so if an Employee only existed as part of a Company, the Employee type could be nested inside the Company struct: https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 1/13 6/26/24, 11:26 AM 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 13.11 — Struct miscellany – Learn C++ #include <iostream> struct Company { struct Employee // accessed via Company::Employee { int id{}; int age{}; double wage{}; }; int numberOfEmployees{}; Employee CEO{}; // Employee is a struct within the Company struct }; int main() { Company myCompany{ 7, { 1, 32, 55000.0 } }; // Nested initialization list to initialize Employee std::cout << myCompany.CEO.wage << '\n'; // print the CEO's wage return 0; } This is more often done with classes, so we’ll talk more about this in a future lesson (15.3 -- Nested types (member types) (https://www.learncpp.com/cpp-tutorial/nested-types-member-types/)2). Structs that are owners should have data members that are owners In lesson 5.11 -- std::string_view (part 2) (https://www.learncpp.com/cpp-tutorial/stdstring_view-part-2/)3, we introduced the dual concepts of owners and viewers. Owners manage their own data, and control when it is destroyed. Viewers view someone else’s data, and do not control when it is altered or destroyed. In most cases, we want our structs (and classes) to be owners of the data they contain. This provides a few useful benefits: The data members will be valid for as long as the struct (or class) is. The value of those data members won’t change unexpectedly. The easiest way to make a struct (or class) an owner is to give each data member a type that is an owner (e.g. not a viewer, pointer, or reference). If a struct or class has data members that are all owners, then the struct or class itself is automatically an owner. If a struct (or class) has a data member that is a viewer, it is possible that the object being viewed by that member will be destroyed before the data member that is viewing it. If this happens, the struct will be left with a dangling member, and accessing that member will lead to undefined behavior. Best practice https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 2/13 6/26/24, 11:26 AM 13.11 — Struct miscellany – Learn C++ In most cases, we want our structs (and classes) to be owners. The easiest way to enable this is to ensure each data member has an owning type (e.g. not a viewer, pointer, or reference). Author’s note Practice safe structs. Don’t let your member dangle. This is why string data members are almost always of type std::string (which is an owner), and not opf type std::string_view (which is a viewer). The following example illustrates a case where this matters: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <string> #include <string_view> struct Owner { std::string name{}; // std::string is an owner }; struct Viewer { std::string_view name {}; // std::string_view is a viewer }; // getName() returns the user-entered string as a temporary std::string // This temporary std::string will be destroyed at the end of the full expression // containing the function call. std::string getName() { std::cout << "Enter a name: "; std::string name{}; std::cin >> name; return name; } int main() { Owner o { getName() }; // The return value of getName() is destroyed just after initialization std::cout << "The owners name is " << o.name << '\n'; // ok Viewer v { getName() }; // The return value of getName() is destroyed just after initialization std::cout << "The viewers name is " << v.name << '\n'; // undefined behavior return 0; } The getName() function returns the name the user entered as a temporary std::string . This temporary return value is destroyed at the end of the full expression in which the function is called. https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 3/13 6/26/24, 11:26 AM 13.11 — Struct miscellany – Learn C++ In the case of o , this temporary std::string is used to initialize o.name . Since o.name is a std::string , o.name makes a copy of the temporary std::string . The temporary std::string then dies, but o.name is not affected since it’s a copy. When we print o.name in the subsequent statement, it works as we expect. In the case of v , this temporary std::string is used to initialize v.name . Since v.name is a std::string_view , v.name is just a view of the temporary std::string , not a copy. The temporary std::string_view then dies, leaving v.name dangling. When we print v.name in the subsequent statement, we get undefined behavior. Struct size and data structure alignment Typically, the size of a struct is the sum of the size of all its members, but not always! Consider the following program: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> struct Foo { short a {}; int b {}; double c {}; }; int main() { std::cout << "The size of short is " << sizeof(short) << " bytes\n"; std::cout << "The size of int is " << sizeof(int) << " bytes\n"; std::cout << "The size of double is " << sizeof(double) << " bytes\n"; std::cout << "The size of Foo is " << sizeof(Foo) << " bytes\n"; return 0; } On the author’s machine, this printed: 1 2 3 4 The size of short is 2 bytes The size of int is 4 bytes The size of double is 8 bytes The size of Foo is 16 bytes Note that the size of short + int + double is 14 bytes, but the size of Foo is 16 bytes! It turns out, we can only say that the size of a struct will be at least as large as the size of all the variables it contains. But it could be larger! For performance reasons, the compiler will sometimes add gaps into structures (this is called padding). https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 4/13 6/26/24, 11:26 AM 13.11 — Struct miscellany – Learn C++ In the Foo struct above, the compiler is invisibly adding 2 bytes of padding after member a , making the size of the structure 16 bytes instead of 14. For advanced readers The reason compilers may add padding is beyond the scope of this tutorial, but readers who want to learn more can read about data structure alignment (https://en.wikipedia.org/wiki/Data_structure_alignment) 4 on Wikipedia. This is optional reading and not required to understand structures or C++! This can actually have a pretty significant impact on the size of the struct, as the following program demonstrates: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> struct Foo1 { short a{}; // will have 2 bytes of padding after a int b{}; short c{}; // will have 2 bytes of padding after c }; struct Foo2 { int b{}; short a{}; short c{}; }; int main() { std::cout << sizeof(Foo1) << '\n'; // prints 12 std::cout << sizeof(Foo2) << '\n'; // prints 8 return 0; } This program prints: 12 8 Note that Foo1 and Foo2 have the same members, the only difference being the declaration order. Yet Foo1 is 50% larger due to the added padding. Tip You can minimize padding by defining your members in decreasing order of size. https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 5/13 6/26/24, 11:26 AM 13.11 — Struct miscellany – Learn C++ The C++ compiler is not allowed to reorder members, so this has to be done manually. Next lesson Back to table of contents Previous lesson 13.12 Member selection with pointers and references 5 6 13.10 Passing and returning structs 7 8 B U URL INLINE CODE C++ CODE BLOCK HELP! Leave a comment... Name* Email* Notify me about replies: POST COMMENT Find a mistake? Leave a comment above! Avatars from https://gravatar.com/11 are connected to your provided email address. 19 COMMENTS Newest Spesader May 18, 2024 12:57 pm https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 6/13 6/26/24, 11:26 AM 13.11 — Struct miscellany – Learn C++ How did no one mention or thank Alex for his advice "Never let your member dangle"? Please pay attention guys. And thank you Alex, I'll be careful. Reply 4 Rohit January 27, 2024 4:51 am Can we say that we should arrange our data members in decreasing order of size so that padding and hence size will be minimum? Last edited 4 months ago by Rohit Reply 1 Alex Author Reply to Rohit 12 January 28, 2024 6:28 pm Yep, exactly that. I'll add to the lesson as a tip. Reply 5 Karl November 29, 2023 12:22 am Is padding something that we need to worry about? I'm suprised that the compiler isn't smart enough to rearrange to order of the members to avoid padding anyway... Reply 1 Alex Author Reply to Karl 13 December 1, 2023 12:12 pm It's not something you need to worry about unless you're allocating millions of objects. 4 Reply Ali October 2, 2023 8:00 pm Hello Alex, https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 7/13 6/26/24, 11:26 AM 13.11 — Struct miscellany – Learn C++ In the example below: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> struct Company { struct Employee // accessed via Company::Employee { int id{}; int age{}; double wage{}; }; int numberOfEmployees{}; Employee CEO{}; // Employee is a struct within the Company struct }; int main() { Company myCompany{ 7, { 1, 32, 55000.0 } }; // Nested initialization list to initialize Employee std::cout << myCompany.CEO.wage << '\n'; // print the CEO's wage return 0; } you've described Employee is accessed via Company::Employee, but for accessing CEO member you've used the syntax myCompany.CEO.wage Would you give an example using Company::Employee ? Reply 0 Alex Author Reply to Ali 14 October 3, 2023 9:11 pm Company::Employee is the fully qualified name of the nested type. If we wanted to define an Employee within main(), we'd use something like Company::Employee e; . The dot syntax is for accessing the members of objects. 3 Reply Yehor August 12, 2023 8:28 pm https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 8/13 6/26/24, 11:26 AM 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 13.11 — Struct miscellany – Learn C++ #include <iostream> struct Company { struct Employee // accessed via Company::Employee { int id{}; int age{}; double wage{}; }; int numberOfEmployees{}; Employee CEO{}; // Employee is a struct within the Company struct }; int main() { Company myCompany{ 7, { 1, 32, 55000.0 } }; // Nested initialization list to initialize Employee std::cout << myCompany.CEO.wage << '\n'; // print the CEO's wage return 0; } When initializing myCompany with { 7, { 1, 32, 55000.0 } }, why is 7 the first argument if it is supposed to be { 1, 32, 55000.0 }? As I see in the definition of struct Company, first is 'struct Employee' and only then 'numberofEmployees'. Thank you! Reply 0 Alex Author Reply to Yehor 15 August 17, 2023 3:49 pm struct Employee is just a type definition. It does not actually instantiate a variable. There are two variables instantiated: numberOfEmployees (first), and CEO (second). We initialize these variables in order. 4 Reply Timo August 4, 2023 1:53 pm https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 9/13 6/26/24, 11:26 AM 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 13.11 — Struct miscellany – Learn C++ #include <iostream> struct Foo1 { short a{}; // will have 2 bytes of padding after a int b{}; short c{}; // will have 2 bytes of padding after c }; struct Foo2 { int b{}; short a{}; short c{}; }; int main() { std::cout << sizeof(Foo1) << '\n'; // prints 12 std::cout << sizeof(Foo2) << '\n'; // prints 8 return 0; } So 32 bits are easier to process than 16 bits by a 64 bit CPU? Also why isn't there any padding for Foo2 done by the compiler? Last edited 10 months ago by Timo Reply 0 Alex Author Reply to Timo 16 August 6, 2023 7:35 pm Alignment boundaries are implementation defined (per compiler/architecture). Members are generally aligned according to its size. A 4-byte int would be aligned on a 4-byte boundary. A 2-byte short would be aligned on a 2-byte boundary. Padding at the end is used so the whole struct is a size that is divisible by the member with the strictest alignment requirements. In Foo1, b must start on a 4-byte boundary. a is only 2 bytes in size, so we get 2 bytes of padding. We also get 2 bytes of padding after c so the size of the struct is divisible by the most strict alignment requirement (int, which has a 4 byte alignment requirement). In Foo2, b must start on a 2-byte boundary. Since a is 4 bytes, and b will naturally fall on a 2-byte boundary. Same with c , it already falls on a 2-byte boundary. No end padding is needed since the struct size is already divisible by the most strict alignment requirement. 6 Reply https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 10/13 6/26/24, 11:26 AM 13.11 — Struct miscellany – Learn C++ Timo Reply to Alex 17 August 7, 2023 3:40 am Ok thank you for the detailed explanation Alex!! Last edited 10 months ago by Timo Reply 1 willwaywang6 July 30, 2023 12:46 am Excuse me, but I'm looking forward to shedding light on the data structure alignment a little more. Reply 1 Alex Author Reply to willwaywang6 18 August 2, 2023 11:17 am This tutorial series doesn't cover the topic further. Read the wikipedia article, and then you can search out from there. 1 Reply Daniel July 25, 2023 5:13 pm Would it be possible to have a member be of type struct A in struct B and have a member be of type struct B in struct A? Like the example below. 1 2 3 4 5 6 7 8 9 10 11 12 13 struct Company { int numberOfEmployees{}; Employee CEO{}; }; struct Employee { Company name{}; int id{}; int age{}; double wage{}; }; https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 11/13 6/26/24, 11:26 AM 13.11 — Struct miscellany – Learn C++ Reply 1 Alex Author Reply to Daniel 19 July 28, 2023 6:49 pm Not like this, as it would result in a circular dependency. If you convert one or both of the members into a pointer (or better, a std::unique_ptr ), then it is possible. Reply 3 Daniel Reply to Alex 20 July 29, 2023 10:30 am Thanks 1 Reply Alok Ahn July 24, 2023 2:56 pm No one must have read this chapter :/ Its quite nullptr out here 1 Reply learnccp lesson learnccp lesson reviewer reviewe July 22, 2023 4:31 pm First LOL 4 Reply Links 1. https://www.learncpp.com/author/Alex/ https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 12/13 6/26/24, 11:26 AM 13.11 — Struct miscellany – Learn C++ 2. https://www.learncpp.com/cpp-tutorial/nested-types-member-types/ 3. https://www.learncpp.com/cpp-tutorial/stdstring_view-part-2/ 4. https://en.wikipedia.org/wiki/Data_structure_alignment 5. https://www.learncpp.com/cpp-tutorial/member-selection-with-pointers-and-references/ 6. https://www.learncpp.com/ 7. https://www.learncpp.com/cpp-tutorial/passing-and-returning-structs/ 8. https://www.learncpp.com/struct-miscellany/ 9. https://www.learncpp.com/cpp-tutorial/constexpr-if-statements/ 10. https://www.learncpp.com/cpp-tutorial/pass-by-const-lvalue-reference/ 11. https://gravatar.com/ 12. https://www.learncpp.com/cpp-tutorial/struct-miscellany/#comment-592947 13. https://www.learncpp.com/cpp-tutorial/struct-miscellany/#comment-590348 14. https://www.learncpp.com/cpp-tutorial/struct-miscellany/#comment-588141 15. https://www.learncpp.com/cpp-tutorial/struct-miscellany/#comment-585516 16. https://www.learncpp.com/cpp-tutorial/struct-miscellany/#comment-585092 17. https://www.learncpp.com/cpp-tutorial/struct-miscellany/#comment-585192 18. https://www.learncpp.com/cpp-tutorial/struct-miscellany/#comment-584781 19. https://www.learncpp.com/cpp-tutorial/struct-miscellany/#comment-584500 20. https://www.learncpp.com/cpp-tutorial/struct-miscellany/#comment-584697 https://www.learncpp.com/cpp-tutorial/struct-miscellany/ 13/13