MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

unions

As you’ve seen, the only difference between struct and class in C++ is that struct defaults to public and class defaults to private. A struct can also have constructors and destructors, as you might expect. But it turns out that a union can also have a constructor, destructor, member functions and even access control:

//: C07:UnionClass.cpp
//  Unions with constructors and member functions
#include<iostream>
using namespace std;

union U {
private: // Access control too!
  int i;
  float f;
public:  
  U(int a) { i = a; }
  U(float b) { f = b;}
  ~U() { f = 0; }
  int read_int() { return i; }
  float read_float() { return f; }
};

int main() {
  U X(12), Y(1.9F);
  cout << X.read_int() << endl;
  cout << Y.read_float() << endl;
} ///:~ 

You might think from the above code that the only difference between a union and a class is the way the data is stored. However, a union cannot be used as a base class during inheritance, which is quite limiting from an object-oriented design standpoint (you’ll learn about inheritance in Chapter XX).

Although the member functions civilize access to the union somewhat, there is still no way to prevent the user from selecting the wrong element type once the union is initialized. A “safe” union can be encapsulated in a class like this. Notice how the enum clarifies the code, and how overloading comes in handy with the constructors:

//: C07:SuperVar.cpp
// A super-variable
#include <iostream>
using namespace std;

class SuperVar {
  enum {
    character,
    integer,
    floating_point
  } vartype;  // Define one
  union {  // Anonymous union
    char c;
    int i;
    float f;
  };
public:
  SuperVar(char ch) {
    vartype = character;
    c = ch;
  }
  SuperVar(int ii) {
    vartype = integer;
    i = ii;
  }
  SuperVar(float ff) {
    vartype = floating_point;
    f = ff;
  }
  void print();
};

void SuperVar::print() {
  switch (vartype) {
    case character:
      cout << "character: " << c << endl;
      break;
    case integer:
      cout << "integer: " << i << endl;
      break;
    case floating_point:
      cout << "float: " << f << endl;
      break;
  }
}

int main() {
  SuperVar A('c'), B(12), C(1.44F);
  A.print();
  B.print();
  C.print();
} ///:~ 

In the above code, the enum has no type name (it is an untagged enumeration). This is acceptable if you are going to immediately define instances of the enum, as is done here. There is no need to refer to the enum’s type in the future, so the type is optional.

The union has no type name and no variable name. This is called an anonymous union , and creates space for the union but doesn’t require accessing the union elements with a variable name and the dot operator. For instance, if your anonymous union is:

union { int i, float f };

you access members by saying:

i = 12;
f = 1.22; 

just like other variables. The only difference is that both variables occupy the same space. If the anonymous union is at file scope (outside all functions and classes) then it must be declared static so it has internal linkage.

Although SuperVar is now safe, its value is a bit dubious because the reason for using a union in the first place is to save space, and the addition of vartype takes up quite a bit of space, so the savings are effectively eliminated. There are a couple of alternatives to make this scheme workable: if the vartype was controlling more than one union instance – if they were all the same type – then you’d only need one for the group and it wouldn’t take up more space. A more useful approach is to have #ifdefs around all the vartype code so you can use it during development and testing to guarantee things are being used correctly, and then eliminate the extra space and time overhead for shipping code.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru