Working With Data
Enums, Structs, and Unions in C++
Table of Contents
Enumerations (Enums)
Enums create named integer constants for better code readability.
Traditional Enums (C-style)
// Basic enum declaration
enum Color {
RED, // 0
GREEN, // 1
BLUE // 2
};
// With explicit values
enum Status {
PENDING = 1,
APPROVED = 2,
REJECTED = 3,
CANCELLED = 100
};
// Usage
Color c = RED;
Status s = APPROVED;
β οΈ Problems with Traditional Enums
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TRADITIONAL ENUM PROBLEMS β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. NO SCOPE - Values pollute enclosing namespace β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β enum Color { RED, GREEN, BLUE }; β β
β β enum TrafficLight { RED, YELLOW, GREEN }; // β ERROR! β β
β β ^^^ ^^^^^ β β
β β Redefinition of RED and GREEN! β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β 2. IMPLICIT CONVERSION - Converts to int silently β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Color c = RED; β β
β β int x = c; // Compiles! x = 0 β β
β β c = 42; // May compile on some compilers! β β
β β if (c == 5) {...} // Compiles! Meaningless comparison β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β 3. NO TYPE SAFETY - Different enums can be compared β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β enum Fruit { APPLE, ORANGE }; β β
β β enum Planet { MARS, VENUS }; β β
β β if (APPLE == MARS) {...} // Compiles! Both are 0 β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Enum Class (C++11) - The Solution β
// Scoped enum (strongly typed)
enum class Color {
Red,
Green,
Blue
};
enum class TrafficLight {
Red, // No conflict with Color::Red
Yellow,
Green // No conflict with Color::Green
};
// Must use scope operator
Color c = Color::Red;
TrafficLight t = TrafficLight::Red;
// No implicit conversion
// int x = c; // β ERROR!
int x = static_cast<int>(c); // β Explicit cast required
// Type-safe comparisons
// if (c == t) {...} // β ERROR! Different types
if (c == Color::Red) {...} // β OK
Enum Class Features
// Specify underlying type
enum class ErrorCode : uint32_t {
None = 0,
NotFound = 404,
ServerError = 500,
MaxValue = 0xFFFFFFFF
};
enum class SmallEnum : uint8_t {
A, B, C, D // Uses only 1 byte
};
// Forward declaration (requires underlying type)
enum class Status : int;
void processStatus(Status s); // Can use before full definition
enum class Status : int {
Active,
Inactive,
Pending
};
Enum Class Visualization
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ENUM vs ENUM CLASS β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Traditional Enum: Enum Class: β
β βββββββββββββββββββββββ βββββββββββββββββββββββ β
β β enum Color { β β enum class Color { β β
β β RED, β β Red, β β
β β GREEN, β β Green, β β
β β BLUE β β Blue β β
β β }; β β }; β β
β βββββββββββββββββββββββ βββββββββββββββββββββββ β
β β β β
β βΌ βΌ β
β βββββββββββββββββββββββ βββββββββββββββββββββββ β
β β Global Namespace β β Color Scope β β
β β RED = 0 β β Color::Red = 0 β β
β β GREEN = 1 β β Color::Green = 1 β β
β β BLUE = 2 β β Color::Blue = 2 β β
β βββββββββββββββββββββββ βββββββββββββββββββββββ β
β β
β Access: RED Access: Color::Red β
β Type: int (implicit) Type: Color (strict) β
β Safe: No Safe: Yes β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Structures (Structs)
Structs group related data together. In C++, structs are almost identical to classes (default public access).
Basic Struct
// Define a struct
struct Point {
double x;
double y;
};
// Create and initialize
Point p1; // Members uninitialized
Point p2 = {3.0, 4.0}; // Aggregate initialization
Point p3{1.0, 2.0}; // C++11 uniform initialization
Point p4 = {.x = 5.0, .y = 6.0}; // C++20 designated initializers
// Access members
p1.x = 10.0;
p1.y = 20.0;
double distance = sqrt(p1.x * p1.x + p1.y * p1.y);
Struct with Methods
struct Rectangle {
double width;
double height;
// Member functions
double area() const {
return width * height;
}
double perimeter() const {
return 2 * (width + height);
}
void scale(double factor) {
width *= factor;
height *= factor;
}
// Static member
static Rectangle square(double side) {
return {side, side};
}
};
// Usage
Rectangle rect{10.0, 5.0};
cout << "Area: " << rect.area() << endl;
rect.scale(2.0);
Rectangle sq = Rectangle::square(5.0);
Struct vs Class
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STRUCT vs CLASS β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββ β
β β struct β β class β β
β βββββββββββββββββββββββββββββ€ βββββββββββββββββββββββββββββ€ β
β β Default access: PUBLIC β β Default access: PRIVATE β β
β β Default inheritance: β β Default inheritance: β β
β β PUBLIC β β PRIVATE β β
β βββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββ β
β β
β Convention: β
β β’ struct - Simple data containers (POD), no invariants β
β β’ class - Complex types with behavior, encapsulation, invariants β
β β
β Examples: β
β struct Point { int x, y; }; // Simple data β
β struct Color { uint8_t r, g, b, a; }; // Simple data β
β class BankAccount { ... }; // Has invariants (balance >= 0) β
β class DatabaseConnection { ... }; // Complex behavior β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Nested Structs
struct Address {
string street;
string city;
string country;
int zipCode;
};
struct Person {
string name;
int age;
Address address; // Nested struct
void print() const {
cout << name << ", " << age << " years old\n";
cout << "Address: " << address.street << ", "
<< address.city << ", " << address.country << endl;
}
};
// Usage
Person p = {
"John Doe",
30,
{"123 Main St", "New York", "USA", 10001}
};
p.print();
POD (Plain Old Data) Structs
// POD struct - can be used with C, memcpy, etc.
struct PodStruct {
int id;
double value;
char name[50];
// No virtual functions, no constructors, no private/protected
};
// Check if POD (C++11)
static_assert(std::is_pod<PodStruct>::value, "PodStruct must be POD");
// Modern check (C++17+)
static_assert(std::is_trivially_copyable_v<PodStruct>);
static_assert(std::is_standard_layout_v<PodStruct>);
Unions
Unions allow storing different types in the same memory location. Only one member can be active at a time.
Basic Union
union Value {
int i;
float f;
char c;
};
// All members share the same memory
Value v;
cout << sizeof(v) << endl; // Size of largest member (int or float = 4)
v.i = 42; // Store int
cout << v.i; // OK: 42
v.f = 3.14f; // Now store float (overwrites int!)
cout << v.f; // OK: 3.14
// cout << v.i; // Undefined behavior! (reading inactive member)
Union Memory Layout
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β UNION MEMORY LAYOUT β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β union Value { β
β int i; // 4 bytes β
β float f; // 4 bytes β
β char c; // 1 byte β
β }; β
β β
β Memory (4 bytes, overlapping): β
β βββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ β
β β Byte 0 β Byte 1 β Byte 2 β Byte 3 β β
β βββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ€ β
β β int i β β Uses all 4 bytes β
β βββββββββββββββββββββββββββββββββββββββββ€ β
β β float f β β Uses all 4 bytes β
β βββββββββββ¬ββββββββββββββββββββββββββββββ€ β
β β char c β (unused) β β Uses only 1 byte β
β βββββββββββ΄ββββββββββββββββββββββββββββββ β
β β
β v.i = 42; [00101010][00000000][00000000][00000000] β
β v.f = 3.14f; [11000011][11110101][01001000][01000000] β
β v.c = 'A'; [01000001][????????][????????][????????] β
β β
β All members START at the SAME address! β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Tagged Union Pattern
// Safe usage with a type tag
enum class ValueType { Int, Float, String };
struct TaggedValue {
ValueType type;
union {
int i;
float f;
// string s; // β Can't have non-trivial types in union
};
void setInt(int val) {
type = ValueType::Int;
i = val;
}
void setFloat(float val) {
type = ValueType::Float;
f = val;
}
void print() const {
switch (type) {
case ValueType::Int: cout << "Int: " << i << endl; break;
case ValueType::Float: cout << "Float: " << f << endl; break;
default: cout << "Unknown type" << endl;
}
}
};
std::variant (C++17) - Type-Safe Union
#include <variant>
#include <string>
// Type-safe alternative to union
std::variant<int, float, std::string> value;
value = 42; // Store int
value = 3.14f; // Store float
value = "hello"; // Store string
// Access with std::get
try {
int i = std::get<int>(value); // Throws if wrong type
} catch (std::bad_variant_access& e) {
cout << "Wrong type!" << endl;
}
// Safe access with std::get_if
if (auto* p = std::get_if<std::string>(&value)) {
cout << "String: " << *p << endl;
}
// Pattern matching with std::visit
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) {
cout << "int: " << arg << endl;
} else if constexpr (std::is_same_v<T, float>) {
cout << "float: " << arg << endl;
} else if constexpr (std::is_same_v<T, std::string>) {
cout << "string: " << arg << endl;
}
}, value);
Bit Fields
Pack multiple values into a single integer for memory efficiency.
Basic Bit Fields
struct StatusFlags {
unsigned int isActive : 1; // 1 bit
unsigned int priority : 3; // 3 bits (0-7)
unsigned int errorCode : 4; // 4 bits (0-15)
unsigned int reserved : 24; // 24 bits
}; // Total: 32 bits = 4 bytes
StatusFlags flags;
flags.isActive = 1;
flags.priority = 5;
flags.errorCode = 12;
// Single integer operations
struct Packed {
uint8_t a : 4; // 4 bits (0-15)
uint8_t b : 4; // 4 bits (0-15)
}; // Total: 1 byte
Bit Field Layout
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BIT FIELD LAYOUT β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β struct StatusFlags { β
β unsigned int isActive : 1; // 1 bit β
β unsigned int priority : 3; // 3 bits β
β unsigned int errorCode : 4; // 4 bits β
β unsigned int reserved : 24; // 24 bits β
β }; β
β β
β 32-bit integer layout: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β reserved (24 bits) βerrβ pri βaβ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββββββ 24 bits βββββββββββββββββββ4βββ3βββ1β β
β bit 31 bit 0 β
β β
β Example: isActive=1, priority=5, errorCode=12 β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β 00000000 00000000 00000000 β1100β 101 β1β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β ββ isActive = 1 β
β β ββββββ priority = 5 (101) β
β ββββββββββ errorCode = 12 (1100) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Practical Bit Field Example
// Network packet header
struct IPv4Header {
uint8_t version : 4; // IP version
uint8_t headerLength : 4; // Header length
uint8_t tos; // Type of service
uint16_t totalLength; // Total length
uint16_t id; // Identification
uint16_t flags : 3; // Flags
uint16_t fragOffset : 13; // Fragment offset
uint8_t ttl; // Time to live
uint8_t protocol; // Protocol
uint16_t checksum; // Header checksum
uint32_t srcAddr; // Source address
uint32_t destAddr; // Destination address
};
// Color with alpha (RGBA)
struct Color32 {
uint8_t r : 8; // Red
uint8_t g : 8; // Green
uint8_t b : 8; // Blue
uint8_t a : 8; // Alpha
}; // Exactly 32 bits = 4 bytes
Recommended Conventions
β Do
// 1. Use enum class instead of plain enum
enum class Direction { North, South, East, West };
// 2. Specify underlying type when needed
enum class ErrorCode : uint16_t { OK = 0, Error = 1 };
// 3. Use structs for simple data aggregates
struct Point { double x, y; };
// 4. Use std::variant instead of union (C++17+)
std::variant<int, double, string> value;
// 5. Initialize all struct members
Point p = {0.0, 0.0}; // or Point p{};
// 6. Use designated initializers (C++20)
Point p = {.x = 1.0, .y = 2.0};
β Don't
// 1. Don't use C-style enums
enum Color { RED, GREEN, BLUE }; // Use enum class
// 2. Don't compare different enum types
// if (Color::Red == Direction::North) // Won't compile with enum class β
// 3. Don't rely on union type-punning (undefined behavior)
union { int i; float f; } u;
u.i = 42;
// float bad = u.f; // UB! Use std::bit_cast or memcpy
// 4. Don't forget struct initialization
Point p; // x and y are uninitialized garbage!
// 5. Don't use bit fields for portable binary formats
// (bit layout is implementation-defined)
Memory Comparison
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STRUCT vs UNION vs BIT FIELD β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β STRUCT (sequential layout) Size = sum of members + padding β
β βββββββββ¬ββββββββ¬ββββββββββββββββββββ β
β β int a β int b β int c β = 12 bytes β
β β(4 B) β(4 B) β (4 B) β β
β βββββββββ΄ββββββββ΄ββββββββββββββββββββ β
β β
β UNION (overlapping) Size = largest member β
β βββββββββββββββββββββββββββββββββββββ β
β β int a OR int b OR int c β = 4 bytes β
β β (4 B) β β
β βββββββββββββββββββββββββββββββββββββ β
β β
β BIT FIELD (packed bits) Size = minimal for bits β
β βββββββββββββββββββββββββββββββββββββ β
β β a:10 β b:10 β c:10 β pad:2 β = 4 bytes (32 bits) β
β βββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Cheat Sheet
// Enum class (preferred)
enum class Name : type { Value1, Value2 };
Name n = Name::Value1;
int i = static_cast<int>(n);
// Struct
struct Name {
type member;
type method() const { return member; }
};
Name obj = {value};
Name obj{value};
// Union (prefer std::variant)
union Name {
type1 a;
type2 b;
};
// std::variant (C++17)
std::variant<type1, type2> v;
std::get<type1>(v);
std::get_if<type1>(&v);
std::visit(visitor, v);
// Bit field
struct Name {
type member : bits;
};
Compile & Run
g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples
# Check struct sizes
g++ -std=c++17 -Wall check_sizes.cpp -o check_sizes
./check_sizes