Union declaration
Una unión es un tipo de clase especial que puede contener solo uno de sus data members no estáticos a la vez.
Contenidos |
Sintaxis
La especificación de clase para una declaración de union es similar a la declaración de class o struct :
union
attr
class-head-name
{
member-specification
}
|
|||||||||
| attr | - | (since C++11) secuencia opcional de cualquier número de attributes |
| class-head-name | - | el nombre de la unión que se está definiendo. Opcionalmente precedido por nested-name-specifier (secuencia de nombres y operadores de resolución de ámbito, terminando con operador de resolución de ámbito). El nombre puede omitirse, en cuyo caso la unión es anónima |
| member-specification | - | lista de especificadores de acceso, declaraciones y definiciones de objetos miembro y funciones miembro. |
Una unión puede tener funciones miembro (incluyendo constructores y destructores), pero no funciones virtuales.
Una unión no puede tener clases base y no puede ser utilizada como clase base.
|
Como máximo un miembro variante puede tener un inicializador de miembro predeterminado . |
(since C++11) |
Una unión no puede tener miembros de datos no estáticos de tipos referencia.
|
Las uniones no pueden contener un miembro de datos no estático con una función miembro especial no trivial. |
(until C++11) |
|
Si una unión contiene un miembro de datos no estático con una función miembro especial no trivial, la correspondiente función miembro especial de la unión puede estar definida como eliminada, consulte la página de funciones miembro especiales correspondiente para más detalles. |
(since C++11) |
Al igual que en la struct declaración, el acceso predeterminado de miembros en una union es public .
Explicación
La unión es al menos tan grande como es necesario para contener su miembro de datos más grande, pero generalmente no es más grande. Los otros miembros de datos están destinados a ser asignados en los mismos bytes como parte de ese miembro más grande. Los detalles de esa asignación están definidos por la implementación, excepto que todos los miembros de datos no estáticos tienen la misma dirección. Es comportamiento indefinido leer desde el miembro de la unión que no fue escrito más recientemente. Muchos compiladores implementan, como una extensión de lenguaje no estándar, la capacidad de leer miembros inactivos de una unión.
#include <cstdint> #include <iostream> union S { std::int32_t n; // ocupa 4 bytes std::uint16_t s[2]; // ocupa 4 bytes std::uint8_t c; // ocupa 1 byte }; // toda la unión ocupa 4 bytes int main() { S s = {0x12345678}; // inicializa el primer miembro, s.n es ahora el miembro activo // En este punto, leer desde s.s o s.c es comportamiento indefinido, // pero la mayoría de compiladores lo definen. std::cout << std::hex << "s.n = " << s.n << '\n'; s.s[0] = 0x0011; // s.s es ahora el miembro activo // En este punto, leer desde s.n o s.c es comportamiento indefinido, // pero la mayoría de compiladores lo definen. std::cout << "s.c is now " << +s.c << '\n' // 11 o 00, dependiendo de la plataforma << "s.n is now " << s.n << '\n'; // 12340011 o 00115678 }
Salida posible:
s.n = 12345678 s.c is now 0 s.n is now 115678
Cada miembro se asigna como si fuera el único miembro de la clase.
|
Si los miembros de una unión son clases con constructores y destructores definidos por el usuario, para cambiar el miembro activo, generalmente se necesitan destructor explícito y placement new:
Ejecutar este código
#include <iostream> #include <string> #include <vector> union S { std::string str; std::vector<int> vec; ~S() {} // needs to know which member is active, only possible in union-like class }; // the whole union occupies max(sizeof(string), sizeof(vector<int>)) int main() { S s = {"Hello, world"}; // at this point, reading from s.vec is undefined behavior std::cout << "s.str = " << s.str << '\n'; s.str.~basic_string(); new (&s.vec) std::vector<int>; // now, s.vec is the active member of the union s.vec.push_back(10); std::cout << s.vec.size() << '\n'; s.vec.~vector(); } Salida: s.str = Hello, world 1 |
(desde C++11) |
Si dos miembros de una unión son standard-layout types, está bien definido examinar su subsecuencia común en cualquier compilador.
Duración de vida de los miembros
La duración de un miembro de unión comienza cuando el miembro se activa. Si otro miembro estaba activo previamente, su duración finaliza.
Cuando el miembro activo de una unión es cambiado mediante una expresión de asignación de la forma
E1 = E2
que utiliza ya sea el operador de asignación incorporado o un operador de asignación trivial, para cada miembro de unión X que aparece en las subexpresiones de acceso a miembro y subíndice de array de
E1
que no sea una clase con constructores por defecto no triviales o eliminados, si la modificación de X tendría comportamiento indefinido bajo las reglas de aliasing de tipos, un objeto del tipo de X es creado implícitamente en el almacenamiento nominado; no se realiza ninguna inicialización y el comienzo de su tiempo de vida es secuenciado después del cálculo de valor de los operandos izquierdo y derecho y antes de la asignación.
union A { int x; int y[4]; }; struct B { A a; }; union C { B b; int k; }; int f() { C c; // no inicia el tiempo de vida de ningún miembro de la unión c.b.a.y[3] = 4; // OK: "c.b.a.y[3]", nombra los miembros de unión c.b y c.b.a.y; // Esto crea objetos para contener los miembros de unión c.b y c.b.a.y return c.b.a.y[3]; // OK: c.b.a.y se refiere al objeto recién creado } struct X { const int a; int b; }; union Y { X x; int k; }; void g() { Y y = {{1, 2}}; // OK, y.x es el miembro activo de la unión int n = y.x.a; y.k = 4; // OK: finaliza el tiempo de vida de y.x, y.k es el miembro activo de la unión y.x.b = n; // comportamiento indefinido: y.x.b modificado fuera de su tiempo de vida, // "y.x.b" nombra y.x, pero el constructor por defecto de X está eliminado, // por lo que el tiempo de vida del miembro de unión y.x no inicia implícitamente }
Constructor de movimiento trivial, operador de asignación de movimiento, (desde C++11) constructor de copia y operador de asignación de copia de tipos union copian las representaciones de objetos. Si el origen y el destino no son el mismo objeto, estas funciones miembro especiales inician el tiempo de vida de cada objeto (excepto para objetos que no son subobjetos del destino ni de tipo de tiempo de vida implícito ) anidado en el destino correspondiente al anidado en el origen antes de que se realice la copia. De lo contrario, no hacen nada. Dos objetos union tienen el mismo miembro activo correspondiente (si existe) después de la construcción o asignación mediante funciones especiales triviales.
Uniones anónimas
Una unión anónima es una definición de unión sin nombre que no define simultáneamente ninguna variable (incluyendo objetos del tipo unión, referencias o punteros a la unión).
union
{
especificación-de-miembros
}
;
|
|||||||||
Las uniones anónimas tienen restricciones adicionales: no pueden tener funciones miembro, no pueden tener miembros de datos estáticos, y todos sus miembros de datos deben ser públicos. Las únicas declaraciones permitidas son miembros de datos no estáticos
y
static_assert
declarations
(since C++11)
.
Los miembros de una unión anónima se inyectan en el ámbito circundante (y no deben entrar en conflicto con otros nombres declarados allí).
int main() { union { int a; const char* p; }; a = 1; p = "Jennifer"; }
Las uniones anónimas de ámbito de espacio de nombres deben declararse static a menos que aparezcan en un espacio de nombres sin nombre.
Clases de tipo unión
Una clase tipo union es o una union, o una clase (no union) que tiene al menos una union anónima como miembro. Una clase tipo union tiene un conjunto de miembros variantes :
- los miembros de datos no estáticos de sus uniones anónimas miembro;
- además, si la clase tipo unión es una unión, sus miembros de datos no estáticos que no son uniones anónimas.
Las clases tipo unión pueden utilizarse para implementar tagged union .
#include <iostream> // S tiene un miembro de datos no estático (tag), tres miembros enumerador (CHAR, INT, DOUBLE), // y tres miembros variante (c, i, d) struct S { enum{CHAR, INT, DOUBLE} tag; union { char c; int i; double d; }; }; void print_s(const S& s) { switch(s.tag) { case S::CHAR: std::cout << s.c << '\n'; break; case S::INT: std::cout << s.i << '\n'; break; case S::DOUBLE: std::cout << s.d << '\n'; break; } } int main() { S s = {S::CHAR, 'a'}; print_s(s); s.tag = S::INT; s.i = 123; print_s(s); }
Salida:
a 123
|
La biblioteca estándar de C++ incluye std::variant , que puede reemplazar muchos usos de uniones y clases similares a uniones. El ejemplo anterior puede reescribirse como
Ejecutar este código
#include <iostream> #include <variant> int main() { std::variant<char, int, double> s = 'a'; std::visit([](auto x){ std::cout << x << '\n';}, s); s = 123; std::visit([](auto x){ std::cout << x << '\n';}, s); } Salida: a 123 |
(desde C++17) |
Palabras clave
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 1940 | C++11 | las uniones anónimas solo permitían miembros de datos no estáticos |
static_assert
también permitido
|
Referencias
- Estándar C++23 (ISO/IEC 14882:2024):
-
- 11.5 Uniones [class.union]
- Estándar C++20 (ISO/IEC 14882:2020):
-
- 11.5 Uniones [class.union]
- Estándar C++17 (ISO/IEC 14882:2017):
-
- 12.3 Uniones [class.union]
- Estándar C++14 (ISO/IEC 14882:2014):
-
- 9.5 Uniones [class.union]
- Estándar C++11 (ISO/IEC 14882:2011):
-
- 9.5 Unions [class.union]
- Estándar C++03 (ISO/IEC 14882:2003):
-
- 9.5 Uniones [class.union]
- Estándar C++98 (ISO/IEC 14882:1998):
-
- 9.5 Uniones [class.union]
Véase también
|
(C++17)
|
una unión discriminada type-safe
(plantilla de clase) |
|
Documentación de C
para
Declaración Union
|
|