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. You can again see the use and benefit of overloading in the following example:

//: 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);
  U(float b);
  ~U();
  int read_int();
  float read_float();
};

U::U(int a) { i = a; }

U::U(float b) { f = b;}

U::~U() { cout << "U::~U()\n"; }

int U::read_int() { return i; }

float U::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 (that is, the int and float are overlaid on the same piece of storage). 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 client programmer from selecting the wrong element type once the union is initialized. In the above example, you could say X.read_float( ) even though it is inappropriate. However, a “safe” union can be encapsulated in a class. In the following example, 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);
  SuperVar(int ii);
  SuperVar(float ff);
  void print();
};

SuperVar:: SuperVar(char ch) {
  vartype = character;
  c = ch;
}

SuperVar:: SuperVar(int ii) {
  vartype = integer;
  i = ii;
}

SuperVar:: SuperVar(float ff) {
  vartype = floating_point;
  f = ff;
}

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 name in the future, so the type name 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 usefulness 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 relative to the data in the union, 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 which can then guarantee things are being used correctly during development and testing. For shipping code, the extra space and time overhead can be eliminated.

Contents | Prev | Next


Contact: webmaster@codeguru.com
CodeGuru - the website for developers.
[an error occurred while processing this directive]