He aprendido pero realmente no consigo sindicatos. Cada texto C o C ++ que reviso los presenta (a veces de pasada), pero tienden a dar muy pocos ejemplos prácticos de por qué o dónde usarlos. ¿Cuándo serían útiles los sindicatos en un caso moderno (o incluso antiguo)? Mis únicas dos conjeturas serían programar microprocesadores cuando tienes un espacio muy limitado para trabajar, o cuando estás desarrollando una API (o algo similar) y quieres obligar al usuario final a tener solo una instancia de varios objetos / tipos en una vez. ¿Estas dos conjeturas están incluso cerca de la derecha?
133
Respuestas:
Los sindicatos se usan generalmente con la compañía de un discriminador: una variable que indica cuál de los campos del sindicato es válido. Por ejemplo, supongamos que desea crear su propio tipo de variante :
Entonces lo usarías como:
Esto es en realidad un lenguaje bastante común, especialmente en las partes internas de Visual Basic.
Para un ejemplo real, vea la unión SDL_Event de SDL . ( código fuente real aquí ). Hay un
type
campo en la parte superior de la unión, y el mismo campo se repite en cada estructura de evento SDL_ *. Luego, para manejar el evento correcto, debe verificar el valor deltype
campo.Los beneficios son simples: hay un solo tipo de datos para manejar todos los tipos de eventos sin usar memoria innecesaria.
fuente
struct object
en github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.cEncuentro 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:
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 deg
arriba, entonces no sabría qué hacer con ella. Lo mismo se aplica a las funciones que devuelven una instancia de unión, veah
: ¿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:
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,
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í:
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 aBx
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:
Luego, para obtener el contenido de una instancia de unión
data
, usea.get(TYPE_B2).b2
y los me gusta, dondea
es unaA
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.
fuente
Un ejemplo es en el ámbito incrustado, donde cada bit de un registro puede significar algo diferente. Por ejemplo, una unión de un número entero de 8 bits y una estructura con 8 campos de bits de 1 bit separados le permite cambiar un bit o el byte completo.
fuente
void*
o máscaras y cambios.REG |= MASK
yREG &= ~MASK
. Si eso es propenso a errores, póngalos en un#define SETBITS(reg, mask)
y#define CLRBITS(reg, mask)
. No confíe en el compilador para obtener los bits en un orden determinado ( stackoverflow.com/questions/1490092/… )Herb Sutter escribió en GOTW hace unos seis años, con énfasis agregado:
Y para un ejemplo menos útil, vea la pregunta larga pero no concluyente gcc, alias estricto y transmisión a través de una unión .
fuente
Bueno, un ejemplo de caso de uso en el que puedo pensar es este:
Luego puede acceder a las partes separadas de 8 bits de ese bloque de datos de 32 bits; sin embargo, prepárate para ser potencialmente mordido por endianness.
Este es solo un ejemplo hipotético, pero cada vez que desee dividir datos en un campo en partes componentes como esta, podría usar una unión.
Dicho esto, también hay un método que es endian-safe:
Por ejemplo, dado que esa operación binaria será convertida por el compilador a la endianness correcta.
fuente
Algunos usos para los sindicatos:
Ahorro de espacio de almacenamiento cuando los campos dependen de ciertos valores:
Selecciona los archivos de inclusión para usar con tu compilador. Encontrará decenas a cientos de usos de
union
:fuente
Las uniones son útiles cuando se trata con datos de nivel de byte (nivel bajo).
Uno de mis usos recientes fue en el modelado de direcciones IP que se ve a continuación:
fuente
Un ejemplo cuando he usado una unión:
Esto me permite acceder a mis datos como una matriz o los elementos.
He usado una unión para que los diferentes términos apunten al mismo valor. En el procesamiento de imágenes, si estaba trabajando en columnas o ancho o el tamaño en la dirección X, puede ser confuso. Para aliviar este problema, uso una unión para saber qué descripciones van juntas.
fuente
Los sindicatos proporcionan polimorfismo en C.
fuente
void*
que hizo eso ^^Un uso brillante de la unión es la alineación de la memoria, que encontré en el código fuente PCL (Biblioteca de nube de puntos). La estructura de datos única en la API puede apuntar a dos arquitecturas: CPU con soporte SSE y CPU sin soporte SSE. Por ejemplo: la estructura de datos para PointXYZ es
Los 3 flotadores están acolchados con un flotador adicional para la alineación SSE. Entonces para
El usuario puede acceder a point.data [0] o point.x (según el soporte de SSE) para acceder, por ejemplo, a la coordenada x. Más detalles de uso similares están en el siguiente enlace: Documentación PCL Tipos PointT
fuente
La
union
palabra clave, aunque todavía se usa en C ++ 03 1 , es principalmente un remanente de los días C. El problema más evidente es que solo funciona con POD 1 .La idea de la unión, sin embargo, todavía está presente, y de hecho las bibliotecas Boost presentan una clase similar a la unión:
Que tiene la mayoría de los beneficios de
union
(si no todos) y agrega:En la práctica, se ha demostrado que era equivalente a una combinación de
union
+enum
, y se comparó que era tan rápido (aunqueboost::any
es más del ámbitodynamic_cast
, ya que usa RTTI).1 Las uniones se actualizaron en C ++ 11 ( uniones sin restricciones ) y ahora pueden contener objetos con destructores, aunque el usuario debe invocar el destructor manualmente (en el miembro de la unión actualmente activo). Todavía es mucho más fácil usar variantes.
fuente
boost::variant
para tratar de usar uniones por su cuenta. Hay demasiados comportamientos indefinidos en torno a los sindicatos que sus posibilidades de hacerlo bien son abismales.Del artículo de Wikipedia sobre sindicatos :
fuente
En los primeros días de C (por ejemplo, como se documentó en 1974), todas las estructuras compartían un espacio de nombres común para sus miembros. Cada nombre de miembro estaba asociado con un tipo y un desplazamiento; si "wd_woozle" fuera un "int" en el desplazamiento 12, entonces un puntero
p
de cualquier tipo de estructurap->wd_woozle
sería equivalente a*(int*)(((char*)p)+12)
. El lenguaje requería que todos los miembros de todos los tipos de estructuras tuvieran nombres únicos, excepto que permitía explícitamente la reutilización de los nombres de los miembros en los casos en que cada estructura donde se usaban los trataba como una secuencia inicial común.El hecho de que los tipos de estructura pudieran usarse promiscuamente hizo posible que las estructuras se comportaran como si contuvieran campos superpuestos. Por ejemplo, definiciones dadas:
el código podría declarar una estructura de tipo "float1" y luego usar "miembros" b0 ... b3 para acceder a los bytes individuales que contiene. Cuando se cambiaba el idioma para que cada estructura recibiera un espacio de nombres separado para sus miembros, el código que se basaba en la capacidad de acceder a las cosas de varias maneras se rompería. Los valores de separar espacios de nombres para diferentes tipos de estructuras fueron suficientes para requerir que dicho código se cambiara para acomodarlo, pero el valor de tales técnicas fue suficiente para justificar la extensión del lenguaje para continuar admitiéndolo.
Código que había sido escrita para explotar la capacidad de acceder al almacenamiento dentro de una
struct float1
, como si se tratara de unastruct byte4
podría ser hecho para trabajar en el nuevo idioma mediante la adición de una declaración:union f1b4 { struct float1 ff; struct byte4 bb; };
, declarar objetos de tipounion f1b4;
en lugar destruct float1
, y la sustitución de los accesos af0
,b0
,b1
, etc. . conff.f0
,bb.b0
,bb.b1
, etc Mientras que hay mejores maneras de dicho código podría haber sido admitidas, elunion
enfoque era al menos algo viable, al menos con las interpretaciones C89 de la era de las reglas de alias.fuente
Digamos que tiene n diferentes tipos de configuraciones (solo es un conjunto de variables que definen parámetros). Al usar una enumeración de los tipos de configuración, puede definir una estructura que tenga la ID del tipo de configuración, junto con una unión de todos los diferentes tipos de configuraciones.
De esta manera, donde sea que pase la configuración puede usar la ID para determinar cómo interpretar los datos de configuración, pero si las configuraciones fueran enormes, no se vería obligado a tener estructuras paralelas para cada tipo de espacio de desperdicio potencial.
fuente
La reciente regla de aliasing introducida en la versión reciente del estándar C ha dado un impulso reciente a la importancia ya elevada de los sindicatos .
Puedes usar uniones de unión para teclear sin infringir el estándar C.
Este programa tiene un comportamiento no especificado (porque lo asumí
float
yunsigned int
tengo la misma duración) pero no un comportamiento indefinido (ver aquí ).fuente
Me gustaría agregar un buen ejemplo práctico para usar union: implementar calculadora / intérprete de fórmulas o usar algún tipo de cálculo (por ejemplo, desea usar modificables durante las partes de tiempo de ejecución de sus fórmulas informáticas - resolver ecuaciones numéricas - solo por ejemplo). Por lo tanto, es posible que desee definir números / constantes de diferentes tipos (entero, punto flotante, incluso números complejos) como este:
Por lo tanto, está ahorrando memoria y lo que es más importante: evita cualquier asignación dinámica para una cantidad probablemente extrema (si usa muchos números definidos en tiempo de ejecución) de objetos pequeños (en comparación con implementaciones a través de herencia de clase / polimorfismo). Pero lo que es más interesante, aún puede usar el poder del polimorfismo C ++ (si es fanático del doble despacho, por ejemplo;) con este tipo de estructura. Simplemente agregue el puntero de interfaz "ficticio" a la clase primaria de todos los tipos de números como un campo de esta estructura, señalando esta instancia en lugar de / además del tipo sin formato, o use buenos punteros de función C antiguos.
por lo que puede usar polimorfismo en lugar de verificaciones de tipo con switch (tipo), con implementación eficiente en memoria (sin asignación dinámica de objetos pequeños), si lo necesita, por supuesto.
fuente
Desde http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :
fuente
Los sindicatos proporcionan una forma de manipular diferentes tipos de datos en una sola área de almacenamiento sin incorporar información independiente de la máquina en el programa. Son análogos a los registros de variantes en pascal
Como un ejemplo como el que se puede encontrar en un administrador de tabla de símbolos del compilador, suponga que una constante puede ser un int, un flotante o un puntero de caracteres. El valor de una constante particular debe almacenarse en una variable del tipo adecuado, pero es más conveniente para la gestión de tablas si el valor ocupa la misma cantidad de almacenamiento y se almacena en el mismo lugar, independientemente de su tipo. Este es el propósito de una unión, una variable única que puede contener legítimamente uno de varios tipos. La sintaxis se basa en estructuras:
La variable u será lo suficientemente grande como para contener el mayor de los tres tipos; El tamaño específico depende de la implementación. Cualquiera de estos tipos puede asignarse a u y luego usarse en expresiones, siempre que el uso sea consistente
fuente