Como ya ha visto, la única diferencia en C++ entre
struct
y class
es que struct
pone
todo por defecto a public
y la clase pone todo por
defecto a private
. Una struct
también puede
tener constructores y destructores, como cabía esperar. Pero
resulta que el tipo union
también puede tener
constructores, destructores, métodos e incluso controles de
acceso. Puede ver de nuevo la utilización y las ventajas de la
sobrecarga de funciones en el siguiente ejemplo:
//: 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; } ///:~
Listado 7.6. C07/UnionClass.cpp
Podría pensar que en el código anterior la única diferencia
entre una unión y una clase es la forma en que los datos se
almacenan en memoria (es decir, el int
y el
float
están superpuestos). Sin embargo una unión no se
puede utilizar como clase base durante la herencia, lo cual
limita bastante desde el punto de vista del diseño orientado a
objetos (veremos la herencia en el Capítulo 14).
Aunque los métodos civilizan ligeramente el tratamiento de
uniones, sigue sin haber manera alguna de prevenir que el
programador cliente seleccione el tipo de elemento equivocado
una vez que la unión se ha inicializado. En el ejemplo anterior,
podría escribir X.read_float()
incluso aunque sea
inapropiado. Sin embargo, una unión «segura» se
puede encapsular en una clase. En el siguiente ejemplo, vea cómo
la enumeración clarifica el código, y cómo la sobrecarga viene
como anillo al dedo con los constructores:
//: 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(); } ///:~
Listado 7.7. C07/SuperVar.cpp
En ese ejemplo la enumeración no tiene nombre de tipo (es una enumeración sin etiqueta). Esto es aceptable si va a definir inmediatamente un ejemplar de la enumeración, tal como se hace aquí. No hay necesidad de indicar el nombre del tipo de la enumeración en el futuro, por lo que aquí el nombre de tipo es opcional.
La unión no tiene nombre de tipo ni nombre de variable. Esto se denomina unión anónima, y crea espacio para la unión pero no requiere acceder a los elementos de la unión con el nombre de la variable y el operador punto. Por ejemplo, si su unión anónima es:
//: C07:AnonymousUnion.cpp int main() { union { int i; float f; }; // Access members without using qualifiers: i = 12; f = 1.22; } ///:~
Listado 7.8. C07/AnonymousUnion.cpp
Note que accede a los miembros de una unión anónima igual que si fueran variables normales. La única diferencia es que ambas variables ocupan el mismo espacio de memoria. Si la unión anónima está en el ámbito del fichero (fuera de todas las funciones y clases), entonces se ha de declarar estática para que tenga enlace interno.
Aunque ahora SuperVar
es segura, su
utilidad es un poco dudosa porque la razón de utilizar una unión
principalmente es la de ahorrar memoria y la adición de
vartype
hace que ocupe bastante espacio en la
unión (relativamente), por lo que la ventaja del ahorro
desaparece. Hay un par de alternativas para que este esquema
funcione. Si vartype
controlara más de una
unión (en el caso de que fueran del mismo tipo) entonces sólo
necesitaría uno para el grupo y no ocuparía más memoria. Una
aproximación más útil es tener #ifdef
s alrededor del
código de vartype
, el cual puede entonces
garantizar que las cosas se utilizan correctamente durante el
desarrollo y las pruebas. Si el código ha de entregarse, antes
puede eliminar las sobrecargas de tiempo y memoria.