Namespaces
Variants

Union declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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:

#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

#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

union

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