18.5. Union: A Space-Saving ClassA union is a special kind of class. A union may have multiple data members, but at any point in time, only one of the members may have a value. When a value is assigned to one member of the union, all other members become undefined. The amount of storage allocated for a union is at least as much as the amount necessary to contain its largest data member. Like any class, a union defines a new type. Defining a UnionUnions offer a convenient way to represent a set of mutually exclusive values that may have different types. As an example, we might have a process that handles different kinds of numeric or character data. That process might define a union to hold these values: // objects of type TokenValue have a single member, // which could be of any of the listed types union TokenValue { char cval; int ival; double dval; }; A union is defined starting with the keyword union, followed by an (optional) name for the union and a set of member declarations enclosed in curly braces. This code defines a union named TokenValue that can hold a value that is either a char, an int, a pointer to char, or a double. Section 18.5 (p. 795) will look at what it means to omit the union name. Like any class, a union type defines how much storage is associated with objects of its type. The size of each union object is fixed at compile time: It is at least as large as the size of the union's largest data member. No Static, Reference, or Class Data MembersSome, but not all, class features apply equally to unions. For example, like any class, a union can specify protection labels to make members public, private, or protected. By default, unions behave like structs: Unless otherwise specified, the members of a union are public. A union may also define member functions, including constructors and destructors. However, a union may not serve as a base class, so a member function may not be virtual. A union cannot have a static data member or a member that is a reference. Moreover, unions cannot have a member of a class type that defines a constructor, destructor, or assignment operator: union illegal_members { Screen s; // error: has constructor static int is; // error: static member int &rfi; // error: reference member Screen *ps; // ok: ordinary built-in pointer type }; This restriction includes classes with members that have a constructor, destructor, or assignment operator. Using a Union TypeThe name of a union is a type name: TokenValue first_token = {'a'}; // initialized TokenValue TokenValue last_token; // uninitialized TokenValue object TokenValue *pt = new TokenValue; // pointer to a TokenValue object Like other built-in types, by default unions are uninitialized. We can explicitly initialize a union in the same way that we can explicitly initialize (Section 12.4.5, p. 464) simple classes. However, we can provide an initializer only for the first member. The initializer must be enclosed in a pair of curly braces. The initialization of first_token gives a value to its cval member. Using Members of a UnionThe members of an object of union type are accessed using the normal member access operators (. and ->): last_token.cval = 'z'; pt->ival = 42; Giving a value to a data member of a union object makes the other data members undefined. When using a union, we must always know what type of value is currently stored in the union. Retrieving the value stored in the union through the wrong data member can lead to a crash or other incorrect program behavior.
Nested UnionsMost often unions are used as nested types, where the discriminant is a member of the enclosing class: class Token { public: // indicates which kind of value is in val enum TokenKind {INT, CHAR, DBL}; TokenKind tok; union { // unnamed union char cval; int ival; double dval; } val; // member val is a union of the 3 listed types }; In this class, the enumeration object tok serves to indicate which kind of value is stored in the val member. That member is an (unnamed) union that holds either a char, int, or double. We often use a switch statement (Section 6.6, p. 199) to test the discriminant and then do processing dependent on which value is currently stored in the union: Token token; switch (token.tok) { case Token::INT: token.val.ival = 42; break; case Token::CHAR: token.val.cval = 'a'; break; case Token::DBL: token.val.dval = 3.14; break; } Anonymous UnionsAn unnamed union that is not used to define an object is referred to as an anonymous union. The names of the members of an anonymous union appear in the enclosing scope. For example, here is our Token class rewritten to use an anonymous union: class Token { public: // indicates which kind of token value is in val enum TokenKind {INT, CHAR, DBL}; TokenKind tok; union { // anonymous union char cval; int ival; double dval; }; }; Because an anonymous union provides no way to access its members, the members are directly accessible as part of the scope where the anonymous union is defined. Rewriting our previous switch to use the anonymous-union version of our class would look like: Token token; switch (token.tok) { case Token::INT: token.ival = 42; break; case Token::CHAR: token.cval = 'a'; break; case Token::DBL: token.dval = 3.14; break; }
![]() |