A lot of students have trouble comprehending why we would employ a structure that "forgets" old data when a new member is added. This article will explain how C++ unions work, provide you with real-world examples, and go over advanced topics like tagged unions to help you build better, more efficient code.
What are C++ Unions?
A union is a type of data that the user defines, just like a class or struct. The main distinction, though, is how they use memory. In a struct, each member has its own address. In this case, all members have the same memory address.
Key Features of Unions
- Size: The biggest member of a union decides how big it is.
- Memory Sharing: At any given time, only one member can hold a value.
- Efficiency: They help you save space when you have a variable that has to hold several kinds of data, but not at the same time.
How to Declare C++ Unions?
To create a union, you use the union keyword. The syntax looks remarkably like a struct, but the behaviour under the hood is entirely different.
C++
union Data Holder {
int intValue;
float floatValue;
char charValue;
};
In this example, if an integer takes 4 bytes, a float takes 4 bytes, and a char takes 1 byte, the total size of the Data Holder union will be 4 bytes. If this were a struct, it would take at least 9 bytes.
Also Read:
How to Access C++ Unions Members?
In this, all members share the same memory. This means:
Only the last assigned member should be accessed
If you assign a value to one member and then read another member, the result will be incorrect or garbage.
Example:
union Data {
int x;
float y;
};
Data d;
d.x = 10;
cout << d.x; // Correct
d.y = 5.5;
cout << d.x; // Incorrect (garbage value)
This happens because assigning y overwrites the memory previously used by x.
How to Initialise C++ Unions?
You can also initialise a union at the time of declaration. However, only one member can be initialised.
Example:
union Data {
int x;
float y;
};
Data d = {10}; // Initializes only 'x'
Important points:
- Only the first member is initialised by default
- You cannot initialise multiple members at once
Difference Between Unions and Structs
To truly grasp the value of unions in C++, we must look at how they sit in your computer's RAM.
| Feature |
Struct |
Union |
| Memory Keyword |
struct |
union |
| Allocation |
Sum of all members (plus padding) |
Size of the largest member |
| Access |
All members can be accessed at once |
Only one member is active at a time |
| Storage |
Every member has a unique location |
All members share the same location |
C++ Unions Examples
Let’s look at a practical scenario. Imagine you are writing a program for a sensor that either returns a whole number (integer) or a precise decimal (float).
C++
#include <iostream>
using namespace std;
union SensorData {
int basicReading;
float precisionReading;
};
int main() {
SensorData mySensor;
mySensor.basicReading = 10;
cout << "Basic: " << mySensor.basicReading << endl;
mySensor.precisionReading = 25.5;
// Now basicReading is corrupted because precisionReading took over the memory
cout << "Precision: " << mySensor.precisionReading << endl;
return 0;
}
In the examples above, assigning a value to precisionReading overwrites the space previously used by basicReading. If you try to print basic reading after setting the float, you will get a "garbage" value.
What is a C++ Anonymous Union?
These are particularly useful when nested inside a struct. An anonymous union allows you to access its members directly without using the dot operator on the union name itself.
Rules for Anonymous Unions:
- They can't have functions for members.
- They can't have members that are private or protected.
- They must be defined within a scope (like a function or another struct).
Example of anonymous union:
C++
struct Employee {
char* name;
union {
int id_number;
char* id_string;
}; // No name given to this union
};
In this case, you can access emp.id_number directly, which keeps the code clean while still saving memory.
C++ Discriminated Unions
One of the biggest risks with a standard union is that the compiler doesn't track which member is currently "active". If you store a float but try to read an integer, your program won't crash, but the data will be nonsensical.
To fix this, programmers utilise C++ tagged unions, which are also known as C++ discriminated unions. This means putting the union inside a struct and adding an enum "tag" that keeps track of what's within the union.
How to Implement C++ Discriminated Unions?
C++
enum DataType { IS_INT, IS_FLOAT, IS_CHAR };
struct SafeUnion {
DataType type; // The "Tag" or "Discriminant"
union {
int i;
float f;
char c;
} data;
};
void printValue(SafeUnion su) {
if (su.type == IS_INT) cout << su.data.i;
else if (su.type == IS_FLOAT) cout << su.data.f;
else if (su.type == IS_CHAR) cout << su.data.c;
}
By using tagged unions, you gain the memory benefits of a union while maintaining the type safety of a more complex object. This is a common pattern in compiler design and protocol parsing.
Uses of C++ Unions in Programming
While modern computers have plenty of RAM, they remain relevant in several specific fields:
- Embedded Systems: When working with microcontrollers that have only a few kilobytes of memory, every byte counts.
- Network Protocols: Unions are great for parsing packets where a header might define different types of following data.
- Performance Optimisation: Reducing the memory footprint of a large array of objects can lead to better cache performance.
- Hardware Interfacing: Directly mapping memory-mapped I/O registers often requires the flexibility of unions.
Limitations of C++ Unions
Despite their power, unions come with strict rules in C++:
- No Inheritance: You cannot inherit from a union, nor can a union be a base class.
- Constructor Restrictions: You generally cannot have members with non-trivial constructors (like std::string) in a union unless you manage the construction and destruction manually.
Single Value: Remember that a union is not a container for multiple values; it is a single container that can change its "shape".