20 topics
/
← Back to Quick Reference
Topic 14

Structs

Aggregates · Memory Layout · Padding · Special Members · POD · struct vs class

C++17 · Advanced Reference

Structs in C++

01

Structs as value types

A struct groups related data under one name. In C++, structs are nearly identical to classes — the only difference is default member access (publicin structs, private in classes). Structs are idiomatically used for simple data bundles and plain-old-data (POD) types.

Key facts

  1. 1.Members are stored in declaration order in memory.
  2. 2.The compiler inserts padding between members to satisfy alignment requirements — struct size may be larger than the sum of member sizes.
  3. 3.Structs with only public data and no user-provided constructors are aggregates — they support brace initialization directly.
  4. 4.C++20 designated initializers (.field = value) make aggregate init self-documenting.

Initialization rules

  1. 1.Local struct without init: members are indeterminate — use {} to zero-initialize.
  2. 2.Static / global struct: zero-initialized automatically.
  3. 3.Default member values (C++11) apply when no initializer is provided for that member.
  4. 4.Brace init checks for narrowing conversions — prefer over parenthesis init.

Rule of Zero: if a struct only holds value-type members (ints, strings, vectors), define no special members — the compiler generates correct copy, move, and destruction automatically.

Definition, Init & Access

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

// ── Initialization ───────────────────────────────────────────
Point a = {1.0, 2.0};          // aggregate init — in declaration order
Point b{3.0, 4.0};             // brace init (C++11) — preferred
Point c = {.x = 1.0, .y = 2.0}; // designated initializers (C++20)
Point d{};                      // zero-initialized: {0.0, 0.0}
Point e;                        // ⚠ members are indeterminate (local struct)

// ── Access ───────────────────────────────────────────────────
a.x = 10.0;
a.y = 20.0;

Point* ptr = &a;
ptr->x;        // 10.0  — arrow operator: dereference + access
(*ptr).x;      // 10.0  — equivalent but more verbose

// ── Struct with default member values (C++11) ────────────────
struct Config {
  int port     = 8080;
  bool ssl     = false;
  std::string host = "localhost";
};
Config cfg;            // all defaults
Config cfg2{9090};     // port=9090, rest default
Point{}Value-initializes all members to zero. Always prefer over Point() for aggregates.
ptr->xArrow operator — dereferences and accesses in one step. Equivalent to (*ptr).x.
= default valuesC++11: member default values apply when the member has no initializer in the constructor/aggregate init.
designated initC++20: .field = value syntax. Out-of-order fields not allowed — must match declaration order.

Member Functions

03
struct Rectangle {
  double width;
  double height;

  // Member functions — structs can have methods (like classes)
  double area() const {
    return width * height;
  }
  double perimeter() const {
    return 2 * (width + height);
  }

  // Modify members
  void scale(double factor) {
    width  *= factor;
    height *= factor;
  }

  // Static member function — no 'this' pointer
  static Rectangle square(double side) {
    return {side, side};
  }
};

Rectangle r{4.0, 3.0};
r.area();         // 12.0
r.scale(2.0);     // width=8, height=6
Rectangle::square(5.0);   // {5.0, 5.0}
const methodCannot modify data members. Should be applied to any method that is logically read-only.
static methodNo this pointer — called on the type, not an instance. Use for factory functions and utilities.
struct vs classStructs can have full member functions, constructors, and operators — they're identical to classes except for default access.

Memory Layout & Padding

04
// ── Struct layout — members stored in declaration order ──────
struct Packed {
  char   a;    // 1 byte
  int    b;    // 4 bytes — but starts at offset 4 (3 bytes padding!)
  char   c;    // 1 byte
  // 3 bytes padding at end (struct size must be multiple of alignment)
};
sizeof(Packed);   // 12 — not 6!

// ── Reorder to minimize padding ──────────────────────────────
struct Tight {
  int    b;    // 4 bytes at offset 0
  char   a;    // 1 byte at offset 4
  char   c;    // 1 byte at offset 5
  // 2 bytes padding
};
sizeof(Tight);    // 8 — not 12

// ── alignof — alignment requirement ─────────────────────────
alignof(Packed);  // 4 (int's alignment requirement dominates)

// ── #pragma pack / __attribute__((packed)) — force no padding
// ⚠ Accessing misaligned members is UB on some architectures
struct [[gnu::packed]] Wire { char a; int b; };  // size = 5
paddingBytes inserted between members so each member starts at its natural alignment. May add 30–50% to struct size.
reorder trickSort members largest to smallest to minimize padding. Biggest alignment requirement first.
alignof(T)The alignment requirement of T — the address must be a multiple of this value.
packed attributeRemoves padding — struct is as small as possible. Misaligned access is UB on some CPUs (ARM). Use with care.
Declare members largest-to-smallest. Put double and int64_t first, int next, char last. This often eliminates all padding and shrinks the struct.

Special Member Functions

05
struct Vec2 {
  double x, y;

  // Constructor (not needed for aggregates, but adds validation)
  Vec2(double x, double y) : x(x), y(y) {}

  // Copy constructor (compiler generates one automatically)
  Vec2(const Vec2&) = default;

  // Move constructor (C++11)
  Vec2(Vec2&&) = default;

  // Copy assignment
  Vec2& operator=(const Vec2&) = default;

  // Destructor (compiler generates: does nothing for trivial types)
  ~Vec2() = default;

  // Comparison (C++20: default generates == and <=>)
  auto operator<=>(const Vec2&) const = default;

  // Arithmetic
  Vec2 operator+(const Vec2& o) const { return {x+o.x, y+o.y}; }
  Vec2& operator+=(const Vec2& o) { x+=o.x; y+=o.y; return *this; }
};

// Rule of Zero: if you don't manage resources, don't define
// any of the special members — let the compiler generate them
= defaultExplicitly ask the compiler to generate the default implementation. Clearer than omitting it.
= deleteExplicitly disable a special member — e.g., = delete on copy constructor to make type move-only.
Rule of ZeroIf you don't manage a resource, define none of the 5 special members — the compiler's versions are correct.
Rule of FiveIf you define any one of destructor/copy/move, define all five — they interact in non-obvious ways.

Aggregates & Returning Multiple Values

06
// Aggregate: no user-provided constructors, no private/protected
// non-static members, no virtual functions, no base classes (C++17)
// → supports aggregate initialization and structured bindings

struct Color { uint8_t r, g, b, a = 255; };
Color red   = {255, 0, 0};         // a defaults to 255
Color green = {.r=0,.g=255,.b=0};  // C++20 designated init

// ── Structured bindings decompose aggregates (C++17) ─────────
auto [r, g, b, a] = red;

// ── Returning multiple values — use struct ───────────────────
struct MinMax { int min, max; };

MinMax findMinMax(const std::vector<int>& v) {
  return { *std::min_element(v.begin(),v.end()),
           *std::max_element(v.begin(),v.end()) };
}
auto [lo, hi] = findMinMax(data);

// vs std::pair (less readable field names)
// vs std::tuple (even less readable)
// vs out parameters (awkward call syntax)
aggregateNo user-provided constructors, all public members — supports brace init and structured bindings directly.
struct for returnsReturning a named struct is clearer than pair<int,int> — callers know what each field means.
auto [a, b] = sStructured bindings decompose any aggregate in declaration order.

struct vs class & POD

07
//                struct                   class
// Default access  public                   private
// Inheritance     public by default        private by default
// Usage style     data bundles, POD types  encapsulated objects

// ── When to use struct ───────────────────────────────────────
// ✅ Simple data bundles with all public members
// ✅ POD (plain old data) types for C interop, serialization
// ✅ Return-value aggregates (MinMax, ParseResult)
// ✅ Policy/tag types passed as template parameters

// ── When to use class ────────────────────────────────────────
// ✅ Types with invariants to protect (bank account balance)
// ✅ Types that manage resources (file handle, mutex, buffer)
// ✅ Types with significant interface and implementation split

// ── POD (trivial + standard-layout) ─────────────────────────
// Trivial: compiler-generated special members, no virtual functions
// Standard-layout: all members same access, no virtual, C-compatible
#include <type_traits>
std::is_trivial_v<Point>           // true
std::is_standard_layout_v<Point>   // true
std::is_pod_v<Point>               // true (deprecated C++20)
struct defaultMembers and base classes are public by default.
class defaultMembers and base classes are private by default.
POD typeTrivial + standard-layout. Compatible with C memcpy, serialization, and binary protocols.
is_trivial_v<T>True if T has trivial (compiler-generated) special members and no virtual functions.