union
MembersBecause of the complexities involved in constructing and destroying members of class type, union
s with class-type members ordinarily are embedded inside another class. That way the class can manage the state transitions to and from the member of class type. As an example, we’ll add a string
member to our union
. We’ll define our union
as an anonymous union
and make it a member of a class named Token
. The Token
class will manage the union
’s members.
To keep track of what type of value the union
holds, we usually define a separate object known as a discriminant. A discriminant lets us discriminate among the values that the union
can hold. In order to keep the union
and its discriminant in sync, we’ll make the discriminant a member of Token
as well. Our class will define a member of an enumeration type (§ 19.3, p. 832) to keep track of the state of its union
member.
The only functions our class will define are the default constructor, the copy-control members, and a set of assignment operators that can assign a value of one of our union
’s types to the union
member:
class Token {
public:
// copy control needed because our class has a union with a string member
// defining the move constructor and move-assignment operator is left as an exercise
Token(): tok(INT), ival{0} { }
Token(const Token &t): tok(t.tok) { copyUnion(t); }
Token &operator=(const Token&);
// if the union holds a string, we must destroy it; see § 19.1.2 (p. 824)
~Token() { if (tok == STR) sval.~string(); }
// assignment operators to set the differing members of the union
Token &operator=(const std::string&);
Token &operator=(char);
Token &operator=(int);
Token &operator=(double);
private:
enum {INT, CHAR, DBL, STR} tok; // discriminant
union { // anonymous union
char cval;
int ival;
double dval;
std::string sval;
}; // each Token object has an unnamed member of this unnamed union type
// check the discriminant and copy the union member as appropriate
void copyUnion(const Token&);
};
Our class defines a nested, unnamed, unscoped enumeration (§ 19.3, p. 832) that we use as the type for the member named tok
. We defined tok
following the close curly and before the semicolon that ends the definition of the enum
, which defines tok
to have this unnamed enum
type (§ 2.6.1, p. 73).
We’ll use tok
as our discriminant. When the union
holds an int
value, tok
will have the value INT
; if the union
has a string
, tok
will be STR
; and so on.
The default constructor initializes the discriminant and the union
member to hold an int
value of 0
.
Because our union
has a member with a destructor, we must define our own destructor to (conditionally) destroy the string
member. Unlike ordinary members of a class type, class members that are part of a union
are not automatically destroyed. The destructor has no way to know which type the union
holds, so it cannot know which member to destroy.
Our destructor checks whether the object being destroyed holds a string
. If so, the destructor explicitly calls the string
destructor (§ 19.1.2, p. 824) to free the memory used by that string
. The destructor has no work to do if the union
holds a member of any of the built-in types.