In C++, unions are user-defined data types, similar to structures, that allow you to store different data types in the same memory location.
Unlike structures, where each member has its own storage, in unions, all members share the same memory. This means that only one member can contain a value at any time.
Unions are efficient for memory management when you need to work with different types but only one of them needs to hold a value at a given time.
Key Features of Unions
- Shared Memory: All members of a union occupy the same memory space.
- Memory Efficiency: The size of a union is equal to the size of its largest member.
- One Active Member: Only one member can hold a value at a time; setting a new member overwrites the previous value.
Basic Syntax
#include <iostream> using namespace std; union UnionName { Type1 member1; Type2 member2; // ... };
Let’s explore unions with examples demonstrating their key characteristics and usage.
1. Basic Union Definition and Usage
Define a simple union and access its members.
#include <iostream> using namespace std; union Data { int integerValue; float floatValue; char charValue; }; int main() { Data data; // Assign an integer value data.integerValue = 100; cout << "Integer: " << data.integerValue << endl; // Assign a float value (overwrites integer value) data.floatValue = 99.99f; cout << "Float: " << data.floatValue << endl; // Assign a char value (overwrites float value) data.charValue = 'A'; cout << "Character: " << data.charValue << endl; return 0; }
Explanation:
- Data is a union containing three members of different types: integerValue, floatValue, and charValue.
- Each new assignment overwrites the previous one due to shared memory space.
Output:
Integer: 100 Float: 99.99 Character: A
2. Checking the Size of a Union
The size of a union is determined by its largest member, allowing you to save memory space.
#include <iostream> using namespace std; union MixedData { int i; double d; char c; }; int main() { cout << "Size of int: " << sizeof(int) << " bytes" << endl; cout << "Size of double: " << sizeof(double) << " bytes" << endl; cout << "Size of char: " << sizeof(char) << " byte" << endl; cout << "Size of union MixedData: " << sizeof(MixedData) << " bytes" << endl; return 0; }
Explanation:
- MixedData contains int, double, and char types.
- The size of the union is determined by the largest member, double, which typically requires more memory.
Output (may vary based on system architecture):
Size of int: 4 bytes Size of double: 8 bytes Size of char: 1 byte Size of union MixedData: 8 bytes
3. Accessing One Member at a Time
Only one member can hold a value at any time. Accessing multiple members simultaneously may yield unexpected results.
#include <iostream> using namespace std; union Example { int i; float f; }; int main() { Example ex; ex.i = 10; cout << "After setting int i: " << ex.i << endl; ex.f = 5.5f; cout << "After setting float f: " << ex.f << endl; // Accessing ex.i after setting ex.f cout << "Accessing int i after setting float f: " << ex.i << endl; return 0; }
Explanation:
- Setting ex.f after ex.i changes the union’s memory, resulting in undefined behavior when accessing ex.i.
Output (results may vary):
After setting int i: 10 After setting float f: 5.5 Accessing int i after setting float f: (undefined or corrupted value)
4. Using Unions with a Tag for Safe Type Identification
A common pattern is to use an additional tag to identify which type is currently in use.
#include <iostream> using namespace std; union Data { int intValue; float floatValue; char charValue; }; struct TaggedData { enum Type { INT, FLOAT, CHAR } dataType; Data data; }; int main() { TaggedData td; // Assign integer value with tag td.dataType = TaggedData::INT; td.data.intValue = 42; if (td.dataType == TaggedData::INT) { cout << "Integer: " << td.data.intValue << endl; } // Assign float value with tag td.dataType = TaggedData::FLOAT; td.data.floatValue = 3.14f; if (td.dataType == TaggedData::FLOAT) { cout << "Float: " << td.data.floatValue << endl; } return 0; }
Explanation:
- TaggedData contains an enum Type and a union Data.
- The tag (dataType) helps identify which union member is currently in use, preventing undefined behavior.
Output:
Integer: 42 Float: 3.14
5. Using Unions in a Struct for Mixed Data Handling
Unions can be nested within structs to manage different types of data in specific scenarios.
#include <iostream> using namespace std; struct Measurement { char unit; union { int inches; double centimeters; }; }; int main() { Measurement m1 = {'i', .inches = 24}; Measurement m2 = {'c', .centimeters = 60.96}; if (m1.unit == 'i') { cout << "Measurement in inches: " << m1.inches << endl; } if (m2.unit == 'c') { cout << "Measurement in centimeters: " << m2.centimeters << endl; } return 0; }
Explanation:
- Measurement struct includes a union and a unit identifier.
- Based on the unit type, the appropriate union member (inches or centimeters) is accessed.
Output:
Measurement in inches: 24 Measurement in centimeters: 60.96
6. Unions with typedef
Using typedef with unions simplifies the syntax, especially in larger programs.
#include <iostream> using namespace std; typedef union { int intVal; float floatVal; char charVal; } SimpleData; int main() { SimpleData data; data.intVal = 10; cout << "Integer: " << data.intVal << endl; data.floatVal = 5.5f; cout << "Float: " << data.floatVal << endl; return 0; }
Explanation:
- typedef allows us to declare SimpleData without specifying union each time.
- Using typedef with unions enhances readability.
Output:
Integer: 10 Float: 5.5
7. Using Anonymous Unions in Structs
Anonymous unions do not require a name and are used directly within structs. They are automatically treated as part of the surrounding structure.
#include <iostream> using namespace std; struct Container { int id; union { int quantity; float weight; }; // Anonymous union }; int main() { Container item; item.id = 1; item.quantity = 50; cout << "ID: " << item.id << endl; cout << "Quantity: " << item.quantity << endl; item.weight = 12.5f; // Overwrites quantity cout << "Weight: " << item.weight << endl; return 0; }
Explanation:
- An anonymous union is defined within Container without a name.
- Members of the anonymous union can be accessed directly.
Output:
ID: 1 Quantity: 50 Weight: 12.5
8. Using Unions in Arrays
Unions can be stored in arrays to hold various data types in each element.
#include <iostream> using namespace std; union MixedValue { int intVal; float floatVal; }; int main() { MixedValue values[2]; values[0].intVal = 10; values[1].floatVal = 20.5f; cout << "Integer Value: " << values[0].intVal << endl; cout << "Float Value: " << values[1].floatVal << endl; return 0; }
Explanation:
- values is an array of MixedValue unions, where each element can store either an integer or a float.
Output:
Integer Value: 10 Float Value: 20.5
9. Passing Unions to Functions
Unions can be passed to functions by value or by reference, just like other data types.
#include < iostream> using namespace std; union Value { int intValue; float floatValue; }; void printIntValue(const Value& v) { cout << "Integer: " << v.intValue << endl; } int main() { Value v; v.intValue = 42; printIntValue(v); return 0; }
Explanation:
- Value union is passed by reference to the printIntValue function, avoiding copying large data.
Output:
Integer: 42
10. Advanced Union Example: Storing Different Data Types
Unions are especially useful in scenarios like managing different data types in a single variable, as in a basic type-safe variant.
#include <iostream> using namespace std; union Var { int intVal; float floatVal; double doubleVal; }; int main() { Var var; // Store an integer var.intVal = 123; cout << "Integer Value: " << var.intVal << endl; // Store a float var.floatVal = 456.789f; cout << "Float Value: " << var.floatVal << endl; // Store a double var.doubleVal = 987.654321; cout << "Double Value: " << var.doubleVal << endl; return 0; }
Explanation:
- The union Var can store either an int, float, or double, depending on the current usage.
- This approach provides a simple, memory-efficient way to manage different types but is limited to handling one type at a time.
Output:
Integer Value: 123 Float Value: 456.789 Double Value: 987.654321
Summary Table of Union Usage
Feature | Example | Description |
---|---|---|
Basic Union | union Data { int i; float f; }; | Defines a union with different data types |
Size of Union | sizeof(UnionName); | Size is determined by the largest member |
Tag for Type Safety | enum Type { INT, FLOAT } | Use a tag to identify the active member |
Union in Struct | struct { union { … }; } | Nest a union inside a struct |
Typedef with Union | typedef union { int a; } Name; | Simplifies union declaration |
Anonymous Union | struct { union { … }; }; | Direct access to union members without a name |
Union Array | UnionType arr[3]; | Stores multiple union elements |
Passing to Function | void func(UnionType u); | Pass union to function by value or reference |
Advanced Variant | union { int a; float b; double c; } | Use union to create simple type-safe variant |
Key Takeaways
- Unions share memory among all members, making them memory-efficient for scenarios where only one member is active at a time.
- Type safety can be enhanced by using a tag, indicating which union member currently holds a value.
- Anonymous unions simplify code by providing direct access to union members within a structure.
- Unions are ideal for creating basic variant types, where you need to store different data types within the same memory space.