|
|
|
|
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 code above 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 14).
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 example above, 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 code above, 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:
//: C07:AnonymousUnion.cpp
int main() {
union {
int i;
float f;
};
// Access members without using qualifiers:
i = 12;
f = 1.22;
} ///:~
Note that you access members of an
anonymous union just as if they were ordinary 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 controlled 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.
|
|
|