Search

8.4 — Structs

There are many instances in programming where we need more than one variable in order to represent an object. For example, to represent yourself, you might want to store your name, your birthday, your height, your weight, or any other number of characteristics about yourself. You could do so like this:

However, you now have 6 independent variables that are not grouped in any way. If you wanted to pass information about yourself to a function, you’d have to pass each variable individually. Furthermore, if you wanted to store information about someone else, you’d have to declare 6 more variables for each additional person! As you can see, this can quickly get out of control.

Fortunately, C++ allows us to create our own user-defined aggregate data types. An aggregate data type is a data type that groups multiple individual variables together. One of the simplest aggregate data types is the struct. A struct (short for structure) allows us to group variables of mixed data types together into a single unit.

Declaring and defining structs

Because structs are user-defined, we first have to tell the compiler what our struct looks like before we can begin using it. To do this, we declare our struct using the struct keyword. Here is an example of a struct declaration:

This tells the compiler that we are defining a struct named Employee. The Employee struct contains 3 variables inside of it: an int named id, an int named age, and a double named wage. These variables that are part of the struct are called members (or fields). Keep in mind that Employee is just a declaration -- even though we are telling the compiler that the struct will have member variables, no memory is allocated at this time. By convention, struct names start with a capital letter to distinguish them from variable names.

Warning: One of the easiest mistakes to make in C++ is to forget the semicolon at the end of a struct declaration. This will cause a compiler error on the next line of code. Modern compilers like Visual Studio 2010 will give you an indication that you may have forgotten a semicolon, but older or less sophisticated compilers may not, which can make the actual error hard to find.

In order to use the Employee struct, we simply declare a variable of type Employee:

This defines a variable of type Employee named joe. As with normal variables, defining a struct variable allocates memory for that variable.

It is possible to define multiple variables of the same struct type:

Accessing struct members

When we define a variable such as Employee joe, joe refers to the entire struct (which contains the member variables). In order to access the individual members, we use the member selection operator (which is a period). Here is an example of using the member selection operator to initialize each member variable:

As with normal variables, struct member variables are not initialized, and will typically contain junk. We must initialize them manually.

In the above example, it is very easy to tell which member variables belong to Joe and which belong to Frank. This provides a much higher level of organization than individual variables would. Furthermore, because Joe’s and Frank’s members have the same names, this provides consistency across multiple variables of the same struct type.

Struct member variables act just like normal variables, so it is possible to do normal operations on them:

Initializing structs

Initializing structs by assigning values member by member is a little cumbersome, so C++ supports a faster way to initialize structs using an initializer list. This allows you to initialize some or all the members of a struct at declaration time.

If the initializer list does not contain an initializer for some elements, those elements are initialized to a default value (that generally corresponds to the zero state for that type). In the above example, we see that frank.wage gets default initialized to 0.0 because we did not specify an explicit initialization value for it.

Non-static member initialization

It’s possible to give non-static (normal) struct members a default value:

If both non-static member initializer and list-initialization are provided, the list-initialization takes precedence.

In the above example, Rectangle x would be initialized with length and width 2.0.

We talk about what static members are in a later chapter. For now, don’t worry about them.

Assigning to structs

If we wanted to assign values to the members of structs, we can to do so individually:

If you want to assign a new value to all members, this is a pain, particularly for structs with many members. You can also assign values to structs members using an initializer list:

Structs and functions

A big advantage of using structs over individual variables is that we can pass the entire struct to a function that needs to work with the members:

In the above example, we pass an entire Employee struct to printInformation() (by value, meaning the argument is copied into the parameter). This prevents us from having to pass each variable individually. Furthermore, if we ever decide to add new members to our Employee struct, we will not have to change the function declaration or function call!

The above program outputs:

ID:   14
Age:  32
Wage: 24.15

ID:   15
Age:  28
Wage: 18.27

A function can also return a struct, which is one of the few ways to have a function return multiple variables.

This prints:

The point is zero

Nested structs

Structs can contain other structs. For example:

In this case, if we wanted to know what the CEO’s salary was, we simply use the member selection operator twice: myCompany.CEO.wage;

This selects the CEO member from myCompany, and then selects the wage member from within CEO.

You can use nested initializer lists for nested structs:

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 Employee struct, but with fixed-size integers and `id` being half the size of `age`. On many platforms, a double is 8 bytes, so we’d expect Employee to be 2 + 4 + 8 = 14 bytes. To find out the exact size of Employee, we can use the sizeof operator:

On the author’s machine, this prints:

The size of a double is 8
The size of Employee is 16

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

In the Employee struct above, the compiler is invisibly adding 2 bytes of padding after member id, making the size of the structure 16 bytes instead of 14. The reason it does this is beyond the scope of this tutorial, but readers who want to learn more can read about data structure alignment on Wikipedia. This is optional reading and not required to understand structures or C++!

Accessing structs across multiple files

Because struct declarations do not take any memory, if you want to share a struct declaration across multiple files (so you can instantiate variables of that struct type in multiple files), put the struct declaration in a header file, and #include that header file anywhere you need it.

Struct variables are subject to the same rules as normal variables. Consequently, to make a struct variable accessible across multiple files, you can use the extern keyword in the declaration in the header and define the variable in a source file.

Final notes on structs

Structs are very important in C++, as understanding structs is the first major step towards object-oriented programming! Later on in these tutorials, you’ll learn about another aggregate data type called a class, which is built on top of structs. Understanding structs well will help make the transition to classes that much easier.

The structs introduced in this lesson are sometimes called plain old data structs (or POD structs) since the members are all data (variable) members. In the future (when we discuss classes) we’ll talk about other kinds of members.

Quiz time

Question #1


You are running a website, and you are trying to keep track of how much money you make per day from advertising. Declare an advertising struct that keeps track of how many ads you’ve shown to readers, what percentage of ads were clicked on by users, and how much you earned on average from each ad that was clicked. Read in values for each of these fields from the user. Pass the advertising struct to a function that prints each of the values, and then calculates how much you made for that day (multiply all 3 fields together).

Show Solution

Question #2


Create a struct to hold a fraction. The struct should have an integer numerator and an integer denominator member. Declare 2 fraction variables and read them in from the user. Write a function called multiply that takes both fractions, multiplies them together, and returns the result as a decimal number. You do not need to reduce the fraction to its lowest terms. Print the result of the multiplication of the 2 fraction variables.

Show Solution


8.5 -- Random number generation
Index
8.3 -- Enum classes

624 comments to 8.4 — Structs

  • UnknownLearner

    Alex and Nascardriver, many thanks for yours work. I almost got in the point with my example for quiz 1. But I definitely forgot related dividing to 100 and I had exciting income hah. My instance below:

  • Rayyan Khan

    Thanks for these awesome tutorials!
    My solution for Q2, is it fine?

  • yeokaiwei

    Hi Alex,
    Could you touch on unions?

    A union is used in certain libraries for events.

    E.g. sf::Event is a union in the SFML library

    If a union a bad thing to use, could you suggest a better alternative?

    Reference
    sfml-dev.org/tutorials/2.5/window-events.php

    • nascardriver

      Unions are a bad thing to use, because it's easy to run into undefined behavior. `std::variant` is the safe alternative.

      • yeokaiwei

        Then, why does SFML use unions for sf::Event?

        I'm assuming they know their events can easily run into UB.

        "Before dealing with events, it is important to understand what the sf::Event type is, and how to correctly use it. sf::Event is a union, which means that only one of its members is valid at a time (remember your C++ lesson: all the members of a union share the same memory space). The valid member is the one that matches the event type, for example event.key for a KeyPressed event. Trying to read any other member will result in an undefined behavior (most likely: random or invalid values). It it important to never try to use an event member that doesn't match its type.

        sf::Event instances are filled by the pollEvent (or waitEvent) function of the sf::Window class. Only these two functions can produce valid events, any attempt to use an sf::Event which was not returned by successful call to pollEvent (or waitEvent) will result in the same undefined behavior that was mentioned above."

        How should we replace the code in the library without it falling apart?

        • nascardriver

          I'm not an SFML developer, I can't tell you why they're using unions. If I had to guess I'd say this was the easiest way of implementing C++ bindings.

          You can write a wrapper class around `sf::Window::pollEvent()`, `sf::Event`, and `sf::EventType` that exposes a `std::variant` of `std::reference_wrapper` for each `union` member. This isn't pretty, if you want to do this you'll have to work it out yourself. I wouldn't personally do this, rather I'd use the `union` but decompose it as quick as possible.

          • yeokaiwei

            Hi nascardriver,
            Thanks for the reply.

            I'm not sure what you're hinting at with decomposing.

            I'm currently using SFML for practice.

            • nascardriver

              By "decompose" I mean "stop using the union", ie. don't pass the `sf::Event` anywhere else, only its members.
              I suppose you have 1 big `switch` that checks the event type and calls other functions. This `switch` should be the only place in your code that accesses the union, everything else can use specific event types. Doing so minimizes the attention you have to pay to the rest of the code, because there's no way it could access an inactive union member.

              • yeokaiwei

                @nascardriver

                I found another union in another game engine called Simple Directmedia Layer.

                These are all defaults in the static library.

                "The SDL_Event structure is the core of all event handling in SDL. SDL_Event is a union of all event structures used in SDL. Using it is a simple matter of knowing which event type corresponds to which union member."

                References:
                http://wiki.libsdl.org/SDL_Event?highlight=(bCategoryStructb)|(CategoryEvents)

              • yeokaiwei

                @nascardriver

                As for the switch function, I believe you are referring to key presses?

                I assume you are telling me to use

                instead of

                They mentioned that you need to set up something called an "Action Map" for complex controls that involved 2+ inputs simultaneously.

                • nascardriver

                  I'm talking about the `union`. Example from the `sf::Event` doc

                  This is what you should have (But with a `switch` rather than `if`). This `switch` shouldn't forward `event` anywhere, only a specific type, eg. `event.mouseMove`, or subtype thereof, eg. `event.size.width`.

  • Jay R

    Thank you for the response to my comment for the ch 7 quiz! It was exactly what I needed to keep going! :)

    Here is some more code that may not be pretty, but it works! Lol. Now I just need to break the habit of not planning ahead and just writing the code.

  • Medhat Diab

    Question #1:

    Question #2:

  • Husain Patel

    Thanks for these awesome tutorials, You should add more practice questions.

  • Andrew Johnson

    In the section "Struct Size and Data Alignment" should the example be changed such that the employee.id is of type short?  As it stands, an int, then one would expect 16 to be the printed size and whilst the wording is understandable, the example doesn't really represent what is being described/taught.  Perhaps I missed something.

  • heartlessG

    This is my version of question #2, let me know if there are any improvements that could be made!

  • Sahil

    In an earlier lesson you said that using the operator== and operator!= on floating point values can be risky due to precision issues but the program that shows how you can return structs from a function uses the operator== to compare 0.0 with member values. I get that it was a 0.0 == 0.0 comparison so it was bound to be safe, but we could change the function to different values or add such a function.

    My question is when is it SAFE to use operator== and operator!= in relation to floating points. Or should we totally avoid doing that keeping in mind the possibility of future changes to the program, if so I think it'll be better to add a comment to the line of code using equality operator, warning that it is not the best practice to do so but its being done here as the program if just for demonstration purposes.

    Thanks.

  • Trickplay

    So far I find the all concepts used in the solutions to the quizzes have been explained in the previous lessons. This site is really solid in that way of explaining and introducing concepts.

    Now I find the solution for question 1 used an unexplained struct initialization. You explained about the individual value initialization, the use of default values and the way you may use zero initialization. As far as I can see (did I miss that?), you never mentioned you could initialize a struct by passing another instance of the struct inside the initialization braces as you do in solution 1.

    You have a paragraph on initialization, maybe you could at least mention this (in my opinion most important) way of initializing structs.

  • Navigator

    Question 2

  • I do consider all the ideas you've offered in your post. They are really convincing and can definitely work. Still, the posts are very short for newbies. May just you please prolong them a little from subsequent time? Thank you for the post.

  • Tomek

    "We talk about what static members are in chapter 8."
    I am in chapter 8 (?)

  • J34NP3T3R

    in Quiz #2

        // Allocate our first fraction
        const Fraction f1{ getFraction() };
        const Fraction f2{ getFraction() };

    why do they have to be CONST ?

    • nascardriver

      If you don't intend to modify something, make it `const`. It makes the code easier to read, because the reader doesn't have to worry about a value changing somewhere.

  • Math

    Hello!!
    So this is my code for the second question and it works perfectly fine but I got this warning:
    Warning    C26451    Arithmetic overflow: Using operator '*' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator '*' to avoid overflow (io.2)    

    For this part of my code:

    and I don't understand the warning can someone please explain it to me?

    • nascardriver

      `f1.denomenator * f2.denomenator` returns an `int`, which you're then casting to a `double` (Because your performing division with a `double`).
      A `double` has a higher range than an `int`. `f1.denomenator * f2.denomenator` could overflow an `int`. If you cast one of the denomenators to a `double` first, that overflow might not occur.

  • Nailed the 2nd question first try!

    • Waldo Lemmer

      You could make line 25 a bit more readable, but it otherwise looks great!

      > Write a function called multiply that [...] returns the result as a decimal number
      Your multiply function prints immediately. It should return a double instead. This would make it more modular, because you would be able to use it in situations where you don't want to print immediately. Leave the printing to the calling function.

      > first try
      Don't write the whole program before testing whether it works, write it piece by piece (function by function) and test every bit after writing it.

  • My initial implementation asked for the user input in the "main" function, but then bool_wp inspired me to create a struct function to keep the main function cleaner.

  • Mithrohir

    i do not know if there was a mention for this video in here before, but i would strongly recommend it for explanation of structure padding.

    here is the video: https://www.youtube.com/watch?v=aROgtACPjjg

  • Solution for Q.1

  • Alex

    I don't understand why in the solution of question #2 you define the variables f1 and f2 of type const.
    Could you explain why?

    Here's how a wrote the program:

    • nascardriver

      Everything that you don't want to modify should be `const`. Very few lessons follow this rule (I don't think this is a learncpp rule yet) at this point. Making everything `const` prevents you from accidentally modifying an object when you didn't mean to.

      Avoid `static` local variables, `getFractionFromUser` isn't reusable. Instead, make `number` a parameter.

  • jazzes

    I keep getting Warning C26451 - Arithmetic Overflow, even while using static_cast. The program runs fine nonetheless. I didn't quite understand what it is.

    This was the only way I managed to make the warnings disappear entirely. Was it really useful? Or it doesn't matter?

Leave a Comment

Put all code inside code tags: [code]your code here[/code]