Encuentro las uniones C ++ bastante buenas. Parece que las personas generalmente solo piensan en el caso de uso en el que uno quiere cambiar el valor de una instancia de unión "en su lugar" (que, al parecer, solo sirve para ahorrar memoria o realizar conversiones dudosas).
De hecho, los sindicatos pueden ser de gran poder como herramienta de ingeniería de software, incluso cuando nunca se cambia el valor de ninguna instancia sindical .
Caso de uso 1: el camaleón
Con las uniones, puede reagrupar varias clases arbitrarias bajo una sola denominación, lo que no está exento de similitudes con el caso de una clase base y sus clases derivadas. Sin embargo, lo que cambia es lo que puede y no puede hacer con una instancia de unión determinada:
struct Batman;
struct BaseballBat;
union Bat
{
Batman brucewayne;
BaseballBat club;
};
ReturnType1 f(void)
{
BaseballBat bb = {/* */};
Bat b;
b.club = bb;
// do something with b.club
}
ReturnType2 g(Bat& b)
{
// do something with b, but how do we know what's inside?
}
Bat returnsBat(void);
ReturnType3 h(void)
{
Bat b = returnsBat();
// do something with b, but how do we know what's inside?
}
Parece que el programador tiene que estar seguro del tipo de contenido de una instancia de unión determinada cuando quiere usarlo. Es el caso en la función f
anterior. Sin embargo, si una función recibiera una instancia de unión como argumento pasado, como es el caso de g
arriba, entonces no sabría qué hacer con ella. Lo mismo se aplica a las funciones que devuelven una instancia de unión, vea h
: ¿cómo sabe la persona que llama qué hay dentro?
Si una instancia de unión nunca se pasa como un argumento o como un valor de retorno, es probable que tenga una vida muy monótona, con picos de emoción cuando el programador elige cambiar su contenido:
Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;
Y ese es el caso de uso más (no) popular de los sindicatos. Otro caso de uso es cuando una instancia de unión viene junto con algo que le dice su tipo.
Caso de uso 2: "Encantado de conocerte, soy object
deClass
"
Supongamos que un programador elige emparejar siempre una instancia de unión con un descriptor de tipo (dejaré a discreción del lector que imagine una implementación para uno de esos objetos). Esto anula el propósito del sindicato en sí si lo que el programador quiere es ahorrar memoria y que el tamaño del descriptor de tipo no es insignificante con respecto al del sindicato. Pero supongamos que es crucial que la instancia de unión pueda pasarse como un argumento o como un valor de retorno con la persona que llama o la persona que llama sin saber lo que hay dentro.
Entonces el programador tiene que escribir un switch
declaración de flujo de control para distinguir a Bruce Wayne de un palo de madera, o algo equivalente. No es tan malo cuando solo hay dos tipos de contenido en la unión, pero obviamente, la unión ya no escala.
Caso de uso 3:
Como los autores de una recomendación para el estándar ISO C ++ lo pusieron de nuevo en 2008,
Muchos dominios con problemas importantes requieren grandes cantidades de objetos o recursos de memoria limitados. En estas situaciones, conservar el espacio es muy importante, y una unión es a menudo una forma perfecta de hacerlo. De hecho, un caso de uso común es la situación en la que un sindicato nunca cambia su miembro activo durante su vida útil. Se puede construir, copiar y destruir como si fuera una estructura que contiene solo un miembro. Una aplicación típica de esto sería crear una colección heterogénea de tipos no relacionados que no se asignan dinámicamente (tal vez se construyen in situ en un mapa o miembros de una matriz).
Y ahora, un ejemplo, con un diagrama de clase UML:
La situación en inglés simple: un objeto de clase A puede tener objetos de cualquier clase entre B1, ..., Bn, y como máximo uno de cada tipo, siendo n un número bastante grande, digamos al menos 10.
No queremos agregar campos (miembros de datos) a A así:
private:
B1 b1;
.
.
.
Bn bn;
porque n puede variar (es posible que queramos agregar clases Bx a la mezcla), y porque esto causaría un desastre con los constructores y porque los objetos A ocuparían mucho espacio.
Podríamos usar un extraño contenedor de void*
punteros a Bx
objetos con moldes para recuperarlos, pero eso es fugitivo y al estilo C ... pero lo más importante es que nos dejaría con la vida útil de muchos objetos asignados dinámicamente para administrar.
En cambio, lo que se puede hacer es esto:
union Bee
{
B1 b1;
.
.
.
Bn bn;
};
enum BeesTypes { TYPE_B1, ..., TYPE_BN };
class A
{
private:
std::unordered_map<int, Bee> data; // C++11, otherwise use std::map
public:
Bee get(int); // the implementation is obvious: get from the unordered map
};
Luego, para obtener el contenido de una instancia de unión data
, use a.get(TYPE_B2).b2
y los me gusta, donde a
es una A
instancia de clase .
Esto es aún más poderoso ya que los sindicatos no tienen restricciones en C ++ 11. Consulte el documento vinculado anteriormente o este artículo para obtener más detalles.