Initializing structs

The previous lesson showed how to define structs and access their members individually. But assigning each member one by one is tedious:

Book novel{};
novel.title = "1984";
novel.author = "George Orwell";
novel.pages = 328;
novel.year = 1949;

C++ provides cleaner ways to initialize all members at once.

Uninitialized members are dangerous

Without initialization, struct members contain garbage values:

#include <iostream>

struct Dimensions
{
    int width;   // no initializer
    int height;  // no initializer
};

int main()
{
    Dimensions box;  // no initializer here either
    std::cout << box.width << '\n';  // undefined behavior!

    return 0;
}

This program might print anything - or crash. Always initialize your structs.

Aggregate initialization with braces

Structs are aggregates - types containing multiple members. C++ provides aggregate initialization using a braced list:

struct Book
{
    std::string title{};
    std::string author{};
    int pages{};
    int year{};
};

int main()
{
    Book novel{ "1984", "George Orwell", 328, 1949 };

    return 0;
}

Values are assigned to members in declaration order:

  • title gets "1984"
  • author gets "George Orwell"
  • pages gets 328
  • year gets 1949

This is called memberwise initialization.

There's also a copy-list form with =:

Book classic = { "Pride and Prejudice", "Jane Austen", 279, 1813 };

Both work, but prefer the direct brace form without =.

Best Practice
Use direct brace initialization for aggregates: `Book novel{ "1984", "George Orwell", 328, 1949 };`

Providing fewer values than members

You don't need to initialize every member explicitly:

struct Book
{
    std::string title{};
    std::string author{};
    int pages{};
    int year{};
};

int main()
{
    Book partial{ "Dune", "Frank Herbert" };  // pages and year not specified

    return 0;
}

Members without explicit values are initialized based on:

  1. Their default member initializer (if present)
  2. Value initialization (typically zero or empty) otherwise

So partial.pages becomes 0 and partial.year becomes 0.

Empty braces value-initialize everything

An empty initializer list value-initializes all members:

Book blank{};  // title="", author="", pages=0, year=0

This is the safest way to ensure no garbage values. Always use at least empty braces.

Default member initializers

You can specify default values directly in the struct definition:

struct NetworkConfig
{
    std::string host{ "localhost" };
    int port{ 8080 };
    int timeout{ 30 };
    bool secure{ false };
};

int main()
{
    NetworkConfig local{};                    // uses all defaults
    NetworkConfig custom{ "api.example.com", 443, 60, true };  // overrides all
    NetworkConfig partial{ "db.local" };      // host overridden, others use defaults

    return 0;
}

When you provide values during initialization, they override the defaults. When you don't, the defaults apply.

Designated initializers (C++20)

Standard aggregate initialization relies on order. Add a member in the middle, and existing initializers shift:

struct Viewport
{
    int x{};
    int y{};
    int width{};
    int height{};
};

Viewport window{ 0, 0, 800, 600 };  // clear what each value means?

Designated initializers name each member explicitly:

Viewport window{ .x = 0, .y = 0, .width = 800, .height = 600 };

Or with brace initialization:

Viewport window{ .x{ 0 }, .y{ 0 }, .width{ 800 }, .height{ 600 } };

Key rules:

  • Members must appear in declaration order
  • Unmentioned members are value-initialized
// Skip x and y, they'll be value-initialized to 0
Viewport centered{ .width{ 1024 }, .height{ 768 } };

Designated initializers add clarity but also verbosity. Use them when order isn't obvious or when skipping members.

Best Practice
Add new members at the end of struct definitions to avoid breaking existing initializations.

Const and constexpr structs

Struct variables can be const:

struct Point
{
    double x{};
    double y{};
};

int main()
{
    const Point origin{ 0.0, 0.0 };
    // origin.x = 1.0;  // Error: can't modify const

    constexpr Point unit{ 1.0, 1.0 };  // compile-time constant

    return 0;
}

Const structs must be initialized and cannot be modified afterward.

Assignment with initializer lists

After creation, you can reassign all members at once:

struct Color
{
    int r{};
    int g{};
    int b{};
};

int main()
{
    Color background{ 255, 255, 255 };  // white

    background = { 0, 0, 0 };  // now black

    return 0;
}

This performs memberwise assignment - each member is assigned the corresponding value.

To keep some members unchanged, you must provide their current values:

background = { background.r, 128, background.b };  // only change green

With designated initializers (C++20):

background = { .r = background.r, .g = 128, .b = background.b };

Undesignated members get value-initialized (reset to zero), so always include members you want to preserve.

Copying structs

A struct can be initialized from another struct of the same type:

#include <iostream>

struct Rectangle
{
    double width{};
    double height{};
};

int main()
{
    Rectangle original{ 10.0, 5.0 };

    Rectangle copy1 = original;    // copy initialization
    Rectangle copy2(original);     // direct initialization
    Rectangle copy3{ original };   // direct-list initialization

    std::cout << copy1.width << " x " << copy1.height << '\n';

    return 0;
}

All three create independent copies with the same member values. The original and copies are separate objects.

Printing structs with operator<<

Define operator<< to print your structs conveniently:

#include <iostream>
#include <string>

struct Book
{
    std::string title{};
    std::string author{};
    int pages{};
    int year{};
};

std::ostream& operator<<(std::ostream& out, const Book& b)
{
    out << b.title << " by " << b.author << " (" << b.year << ", " << b.pages << " pages)";
    return out;
}

int main()
{
    Book novel{ "The Hobbit", "J.R.R. Tolkien", 310, 1937 };
    std::cout << novel << '\n';

    return 0;
}

Output:

The Hobbit by J.R.R. Tolkien (1937, 310 pages)

The function takes the stream by reference, outputs formatted data, and returns the stream for chaining.

Summary

  • Aggregate initialization uses braced lists to initialize all members at once
  • Memberwise initialization assigns values in declaration order
  • Fewer values than members means remaining members use defaults or value initialization
  • Empty braces {} value-initialize all members safely
  • Default member initializers provide fallback values in the struct definition
  • Designated initializers (C++20) name members explicitly for clarity
  • Const structs must be initialized and cannot be modified
  • Assignment with initializer lists replaces all member values at once
  • Copying structs creates independent objects with identical member values
  • Overloading operator<< enables convenient printing with std::cout