Espacios de nombres
Variantes
Acciones

Declaración de unión

De cppreference.com
< cpp‎ | language
 
 
 
 

Una unión es un tipo clase especial que mantiene solamente uno de sus datos miembro no estáticos a la vez.

El especificador de clase para una declaración de unión es similar a la declaración de una clase o estructura:

union atrib nombre-de-unión { especificación-de-miembros }
atrib(C++11) - Secuencia opcional de cualquier número de atributos
nombre-de-unión - El nombre de la unión que se define. Opcionalmente antepuesto por el nested-name-specifier (secuencia de nombres y operadores de resolución de ámbito, que terminan con el operador de resolución de ámbito). El nombre puede omitirse, en cuyo case la uníon se dice que es sin nombre
especificación-de-miembros - Lista de especificadores de acceso, declaraciones y definiciones de objetos miembro y funciones miembro.

Una unión puede tener funciones miembro (incluidos constructores y destructores), pero no funciones virtuales.

Una unión no puede tener clases base y no puede usarse como clase base.

Una unión no puede tener datos miembro no estáticos de tipos referencia.

Las uniones no pueden contener un dato miembro no estático con una función de miembro especial no trivial (constructor de copia, operador de asignación de copia, o destructor).

(hasta C++11)

Si una unión contiene un dato miembro no estático con una función de miembro especial no trivial (constructor de copia/movimiento, operador de asignación de copia/movimiento, o destructor), esa función se elimina de forma predeterminada en la unión y el programador debe definirla explícitamente.

Si una unión contiene un miembro de datos no estático con un constructor por defecto no trivial, el constructor predeterminado de la unión se elimina por defecto a menos que un miembro variante de la unión tenga un inicializador de miembro por defecto .

Como máximo, un miembro variante puede tener un inicializador de miembro por defecto.

(desde C++11)

Al igual que en la declaración de estructuras, el acceso a miembro predeterminado en una unión es público.

Contenido

[editar] Explicación

La unión es tan grande como sea necesario para mantener a su dato miembro más grande. Los otros datos miembro se asignan 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 pero todos los datos miembro no estáticos tendrán la misma dirección (desde C++14). Es un comportamiento indefinido leer del miembro de la unión que no se escribió 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 <iostream>
#include <cstdint>
union S
{
    std::int32_t n;     // ocupa 4 bytes
    std::uint16_t s[2]; // ocupa 4 bytes
    std::uint8_t c;     // ocupa 1 byte
};                      // la unión completa ocupa 4 bytes
 
int main()
{
    S s = {0x12345678}; // inicializa el primer miembro, s.n es ahora el miembro activo
    // en este punto, leer de s.s o s.c es comportamiento indefinido
    std::cout << std::hex << "s.n = " << s.n << '\n';
    s.s[0] = 0x0011; // s.s es ahora el miembro activo
    // en este punto, leer de n o c es comportamiento indefinido, pero la mayoría de los compiladores lo definen
    std::cout << "s.c es ahora " << +s.c << '\n' // 11 or 00, dependiendo de la plataforma
              << "s.n es ahora " << s.n << '\n'; // 12340011 o 00115678
}

Posible salida:

s.n = 12345678
s.c es ahora 0
s.n es ahora 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 un destructor explícito y new de ubicación.

#include <iostream>
#include <string>
#include <vector>
 
union S
{
    std::string str;
    std::vector<int> vec;
    ~S() {} // necesita saber qué miembro está activo, solo es posible en una clase tipo union 
};          // toda la unión ocupa max(sizeof(string), sizeof(vector<int>))
 
int main()
{
    S s = {"Hola, mundo"};
    // en este punto, leer desde s.vec es comportamiento indefinido
    std::cout << "s.str = " << s.str << '\n';
    s.str.~basic_string();
    new (&s.vec) std::vector<int>;
    // ahora, s.vec es el miembro activo de la unión
    s.vec.push_back(10);
    std::cout << s.vec.size() << '\n';
    s.vec.~vector();
}

Salida:

s.str = Hola, mundo
1
(desde C++11)

Si dos miembros de la unión son tipos de diseño estándar, está bien definido examinar su subsecuencia común en cualquier compilador.

[editar] Duración de miembro

La duración de un miembro de la unión comienza cuando el miembro se activa. Si otro miembro estuvo activo anteriormente, su duración finaliza.

Cuando se cambia el miembro activo de una unión mediante una expresión de asignación de la forma E1 = E2 que utiliza 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 es una clase con constructores no triviales o predeterminados eliminados, si la modificación de X tendría un comportamiento indefinido bajo las reglas de alias de tipo, un objeto del tipo de X se crea implícitamente en el almacenamiento designado; no se realiza la inicialización y el comienzo de su duración se secuencia después del cálculo del 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 comienza la duración de ningún miembro de la unión
  c.b.a.y[3] = 4;    // OK: "c.b.a.y[3]", nombra a los miembros c.b y c.b.a.y;
                     // Esto crea objetos para contener miembros de la unión c.b y c.b.a.y
  return c.b.a.y[3]; // de acuerdo: 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 } }; // de acuerdo, y.x es el miembro activo de la unión (9.2)
  int n = y.x.a;
  y.k = 4;   // de acuerdo: termina la duración 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 duración,
             // "y.x.b" denomina a y.x, pero el constructor por defecto de X se elimina,
             // entonces la vida del miembro de la unión y.x no comienza implícitamente
}

[editar] Uniones anónimas

Una unión anónima es una definición de unión sin nombre que no define simultáneamente ninguna variable (incluidos los objetos del tipo unión, referencias o punteros a la unión).

union { especificación-de-miembro } ;

Las uniones anónimas tienen restricciones adicionales: no pueden tener funciones miembro, no pueden tener datos miembro estáticos, y todos sus datos miembro deben ser públicos. Las únicas declaraciones permitidas son datos miembro no estáticos y declaraciones static_assert (desde C++14).

Los miembros de una unión anónima se inyectan en el ámbito adjunto (y no deben entrar en conflicto con otros nombres declarados allí).

int main()
{
    union
    {
        int a;
        const char* p;
    };
    a = 1;
    p = "Pedro";
}

Las uniones anónimas de ámbito de espacio de nombres deben declararse estáticas (static) a menos que aparezcan en un espacio de nombres sin nombre.

[editar] Clases similares a uniones

Una clase similar a una unión es una clase (no unión) que tiene al menos una unión anónima como miembro o unión. Una clase similar a una unión tiene un conjunto de miembros variantes:

  • los datos miembro no estáticos de sus uniones anónimas miembro;
  • además, si la clase de unión es una unión, sus miembros de datos no estáticos que no son uniones anónimas.

Clases similares a uniones pueden usarse para implementar una unión etiquetada.

#include <iostream>
 
// S tiene un dato miembro no estático (etiqueta), tres miembros enumeradores (CHAR, INT, DOUBLE),
// y tres miembros variantes (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 <variant>
#include <iostream>
 
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)

[editar] Informes de defectos

Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.

ID Aplicado a Comportamiento según lo publicado Comportamiento correcto
CWG 1940 C++14 Las uniones anónimas solamente permitían datos miembro no estáticos. static_assert también se permite

[editar] Véase también

Documentación de C para Declaración de unión