In order to save memory, unions allow you to use the same memory to store objects from a list of possible types as long as one object is stored at
a time.
In C and in C++ prior to C++11, unions are restricted to trivial types.
Starting from C++11, it is possible to use unions with non-trivial types with the following limitations :
- You have to manually handle the lifetime of the active member, using placement new and explicit object destruction.
- You have to define special members like destructor and copy-constructor while taking into consideration the active member.
In some cases, code that fails to perfectly follow those rules may still compile, but lead to memory corruption.
C++17 introduced std::variant
which can replace unions while removing this burden and the associated risk. As a safer and more
readable alternative, they should be preferred.
Noncompliant code example
#include <new> // Required for placement 'new'.
#include <string>
#include <iostream>
using namespace std;
struct IntOrString {
enum {holdsInt, holdsString} currentAlternative;
union {
int z;
string s; // Noncompliant: non-trivial type in Union
};
IntOrString() : currentAlternative{holdsInt} {
z = 0;
}
IntOrString(char const *s) : currentAlternative{holdsString} {
new(&s) string(s);
}
IntOrString(IntOrString const &src) : currentAlternative{src.currentAlternative}{
if (currentAlternative == holdsString) {
new(&s) string(src.s);
}
}
IntOrString &operator=(IntOrString &&) = delete;
~IntOrString() {
if (currentAlternative == holdsString) {
s.~string();
}
}
};
void stringize(IntOrString &ios) {
if (ios.currentAlternative == IntOrString::holdsString) {
return;
}
new (&ios.s) string(std::to_string(ios.z));
}
int main() {
IntOrString ios;
auto copy = ios;
ios.z = 12;
stringize(ios);
std::cout<< ios.s << "\n";
}
Compliant solution
#include <variant>
#include <iostream>
#include <string>
using namespace std;
using IntOrString = variant<int, string>;
void stringize(IntOrString &ios) {
if(auto i = get_if<int>(&ios)) {
ios = to_string(*i);
}
}
int main() {
IntOrString ios = 12;
auto copy = ios;
stringize(ios);
cout << std::get<string>(ios) << '\n';
}