union

Nota:

En C++17 y las versiones posteriores, std::variantclass es una alternativa segura para tipos union.

union es un tipo definido por el usuario en el que todos los miembros comparten la misma ubicación de memoria. Esta definición significa que, en un momento dado, una union no puede contener más de un objeto de su lista de miembros. También significa que, independientemente de cuántos miembros tiene una union, en todo momento usa únicamente la memoria suficiente para almacenar al miembro más grande.

Una union pueden ser útiles para conservar memoria cuando se tienen muchos objetos y memoria limitada. Sin embargo, un union requiere un cuidado adicional para usarse correctamente. Usted es responsable de asegurarse de que siempre tenga acceso al mismo miembro que asignó. Si algún tipo de miembro tiene una constructnotrivial o , debe escribir código para construct explícitamente y destruir ese miembro. Antes de usar una union, considere si el problema que intenta resolver se puede expresar mejor usando una class base y tipos de class derivadas.

Sintaxis

uniontagopt{member-list};

Parámetros

tag
Nombre del tipo asignado a la union.

member-list
Miembros que puede contener la union.

Declarar una union

Comience la declaración de una union mediante el uso de la palabra clave union y agregue la lista de miembros entre llaves:

// declaring_a_union.cpp
union RecordType    // Declare a simple union type
{
    char   ch;
    int    i;
    long   l;
    float  f;
    double d;
    int *int_ptr;
};

int main()
{
    RecordType t;
    t.i = 5; // t holds an int
    t.f = 7.25; // t now holds a float
}

Usar una union

En el ejemplo anterior, el código que acceda a la union debe saber qué miembro mantiene los datos. La solución más común a este problema se denomina una union discriminada. Incluye la union en un elemento struct, e incluye un miembro enum que indica el tipo de miembro almacenado actualmente en union. En el ejemplo siguiente se muestra el patrón básico:

#include <queue>

using namespace std;

enum class WeatherDataType
{
    Temperature, Wind
};

struct TempData
{
    int StationId;
    time_t time;
    double current;
    double max;
    double min;
};

struct WindData
{
    int StationId;
    time_t time;
    int speed;
    short direction;
};

struct Input
{
    WeatherDataType type;
    union
    {
        TempData temp;
        WindData wind;
    };
};

// Functions that are specific to data types
void Process_Temp(TempData t) {}
void Process_Wind(WindData w) {}

void Initialize(std::queue<Input>& inputs)
{
    Input first;
    first.type = WeatherDataType::Temperature;
    first.temp = { 101, 1418855664, 91.8, 108.5, 67.2 };
    inputs.push(first);

    Input second;
    second.type = WeatherDataType::Wind;
    second.wind = { 204, 1418859354, 14, 27 };
    inputs.push(second);
}

int main(int argc, char* argv[])
{
    // Container for all the data records
    queue<Input> inputs;
    Initialize(inputs);
    while (!inputs.empty())
    {
        Input const i = inputs.front();
        switch (i.type)
        {
        case WeatherDataType::Temperature:
            Process_Temp(i.temp);
            break;
        case WeatherDataType::Wind:
            Process_Wind(i.wind);
            break;
        default:
            break;
        }
        inputs.pop();

    }
    return 0;
}

En el ejemplo anterior, la union en el elemento Inputstruct no tiene nombre, por lo que se denomina anónimounion. Se puede acceder a sus miembros directamente como si fueran miembros de struct. Para obtener más información sobre cómo usar un anónimo union, consulte la sección Anónimo union.

En el ejemplo anterior se muestra un problema que también se podría resolver mediante el uso de los tipos class que derivan de una base class común. Puede bifurcar el código en función del tipo en tiempo de ejecución de cada objeto del contenedor. El código puede ser más fácil de mantener y reconocer, pero también más lento que el uso de union. Además, con un union, se pueden almacenar tipos no relacionados. Una union permite cambiar dinámicamente el tipo del valor almacenado sin cambiar el tipo de la propia variable union. Por ejemplo, se podría crear una matriz heterogénea de MyUnionType, cuyos elementos almacenan diferentes valores de tipos diferentes.

Es fácil usar incorrectamente el Inputstruct en el ejemplo. El usuario es quien debe usar el discriminador correctamente para acceder al miembro que contiene los datos. Para evitar el uso indebido, haga que la union privada private y proporcione funciones de acceso especiales, como se muestra en el ejemplo siguiente.

Sin restricción union (C++11)

En C++03 y versiones anteriores, un union objeto puede contener miembros que nostatic tienen un class tipo, siempre y cuando el tipo no tenga operadores de constructos, destructos o de asignación proporcionados por el usuario. En C++11, se quitaron estas restricciones. Si incluye miembros de este tipo en la union, el compilador marcará automáticamente las funciones miembro especiales que no proporcionase el usuario como deleted. Si la union es una union anónima dentro de una class o una struct, entonces cualquier función miembro especial de la class o la struct que no sea proporcionada por el usuario se marca como deleted. En el ejemplo siguiente se muestra cómo manejar ese caso. Uno de los miembros de la union tiene un miembro que requiere este tratamiento especial:

// for MyVariant
#include <crtdbg.h>
#include <new>
#include <utility>

// for sample objects and output
#include <string>
#include <vector>
#include <iostream>

using namespace std;

struct A
{
    A() = default;
    A(int i, const string& str) : num(i), name(str) {}

    int num;
    string name;
    //...
};

struct B
{
    B() = default;
    B(int i, const string& str) : num(i), name(str) {}

    int num;
    string name;
    vector<int> vec;
    // ...
};

enum class Kind { None, A, B, Integer };

#pragma warning (push)
#pragma warning(disable:4624)
class MyVariant
{
public:
    MyVariant()
        : kind_(Kind::None)
    {
    }

    MyVariant(Kind kind)
        : kind_(kind)
    {
        switch (kind_)
        {
        case Kind::None:
            break;
        case Kind::A:
            new (&a_) A();
            break;
        case Kind::B:
            new (&b_) B();
            break;
        case Kind::Integer:
            i_ = 0;
            break;
        default:
            _ASSERT(false);
            break;
        }
    }

    ~MyVariant()
    {
        switch (kind_)
        {
        case Kind::None:
            break;
        case Kind::A:
            a_.~A();
            break;
        case Kind::B:
            b_.~B();
            break;
        case Kind::Integer:
            break;
        default:
            _ASSERT(false);
            break;
        }
        kind_ = Kind::None;
    }

    MyVariant(const MyVariant& other)
        : kind_(other.kind_)
    {
        switch (kind_)
        {
        case Kind::None:
            break;
        case Kind::A:
            new (&a_) A(other.a_);
            break;
        case Kind::B:
            new (&b_) B(other.b_);
            break;
        case Kind::Integer:
            i_ = other.i_;
            break;
        default:
            _ASSERT(false);
            break;
        }
    }

    MyVariant(MyVariant&& other)
        : kind_(other.kind_)
    {
        switch (kind_)
        {
        case Kind::None:
            break;
        case Kind::A:
            new (&a_) A(move(other.a_));
            break;
        case Kind::B:
            new (&b_) B(move(other.b_));
            break;
        case Kind::Integer:
            i_ = other.i_;
            break;
        default:
            _ASSERT(false);
            break;
        }
        other.kind_ = Kind::None;
    }

    MyVariant& operator=(const MyVariant& other)
    {
        if (&other != this)
        {
            switch (other.kind_)
            {
            case Kind::None:
                this->~MyVariant();
                break;
            case Kind::A:
                *this = other.a_;
                break;
            case Kind::B:
                *this = other.b_;
                break;
            case Kind::Integer:
                *this = other.i_;
                break;
            default:
                _ASSERT(false);
                break;
            }
        }
        return *this;
    }

    MyVariant& operator=(MyVariant&& other)
    {
        _ASSERT(this != &other);
        switch (other.kind_)
        {
        case Kind::None:
            this->~MyVariant();
            break;
        case Kind::A:
            *this = move(other.a_);
            break;
        case Kind::B:
            *this = move(other.b_);
            break;
        case Kind::Integer:
            *this = other.i_;
            break;
        default:
            _ASSERT(false);
            break;
        }
        other.kind_ = Kind::None;
        return *this;
    }

    MyVariant(const A& a)
        : kind_(Kind::A), a_(a)
    {
    }

    MyVariant(A&& a)
        : kind_(Kind::A), a_(move(a))
    {
    }

    MyVariant& operator=(const A& a)
    {
        if (kind_ != Kind::A)
        {
            this->~MyVariant();
            new (this) MyVariant(a);
        }
        else
        {
            a_ = a;
        }
        return *this;
    }

    MyVariant& operator=(A&& a)
    {
        if (kind_ != Kind::A)
        {
            this->~MyVariant();
            new (this) MyVariant(move(a));
        }
        else
        {
            a_ = move(a);
        }
        return *this;
    }

    MyVariant(const B& b)
        : kind_(Kind::B), b_(b)
    {
    }

    MyVariant(B&& b)
        : kind_(Kind::B), b_(move(b))
    {
    }

    MyVariant& operator=(const B& b)
    {
        if (kind_ != Kind::B)
        {
            this->~MyVariant();
            new (this) MyVariant(b);
        }
        else
        {
            b_ = b;
        }
        return *this;
    }

    MyVariant& operator=(B&& b)
    {
        if (kind_ != Kind::B)
        {
            this->~MyVariant();
            new (this) MyVariant(move(b));
        }
        else
        {
            b_ = move(b);
        }
        return *this;
    }

    MyVariant(int i)
        : kind_(Kind::Integer), i_(i)
    {
    }

    MyVariant& operator=(int i)
    {
        if (kind_ != Kind::Integer)
        {
            this->~MyVariant();
            new (this) MyVariant(i);
        }
        else
        {
            i_ = i;
        }
        return *this;
    }

    Kind GetKind() const
    {
        return kind_;
    }

    A& GetA()
    {
        _ASSERT(kind_ == Kind::A);
        return a_;
    }

    const A& GetA() const
    {
        _ASSERT(kind_ == Kind::A);
        return a_;
    }

    B& GetB()
    {
        _ASSERT(kind_ == Kind::B);
        return b_;
    }

    const B& GetB() const
    {
        _ASSERT(kind_ == Kind::B);
        return b_;
    }

    int& GetInteger()
    {
        _ASSERT(kind_ == Kind::Integer);
        return i_;
    }

    const int& GetInteger() const
    {
        _ASSERT(kind_ == Kind::Integer);
        return i_;
    }

private:
    Kind kind_;
    union
    {
        A a_;
        B b_;
        int i_;
    };
};
#pragma warning (pop)

int main()
{
    A a(1, "Hello from A");
    B b(2, "Hello from B");

    MyVariant mv_1 = a;

    cout << "mv_1 = a: " << mv_1.GetA().name << endl;
    mv_1 = b;
    cout << "mv_1 = b: " << mv_1.GetB().name << endl;
    mv_1 = A(3, "hello again from A");
    cout << R"aaa(mv_1 = A(3, "hello again from A"): )aaa" << mv_1.GetA().name << endl;
    mv_1 = 42;
    cout << "mv_1 = 42: " << mv_1.GetInteger() << endl;

    b.vec = { 10,20,30,40,50 };

    mv_1 = move(b);
    cout << "After move, mv_1 = b: vec.size = " << mv_1.GetB().vec.size() << endl;

    cout << endl << "Press a letter" << endl;
    char c;
    cin >> c;
}

Una union no puede almacenar una referencia. Tampoco union admite la herencia. Esto significa que no se puede usar union como base class o heredar de otra class, o bien tener funciones virtuales.

Inicialización de un union

Puede declarar e inicializar una union en la misma instrucción mediante la asignación de una expresión entre llaves. La expresión se evalúa y se asigna al primer campo de la union.

#include <iostream>
using namespace std;

union NumericType
{
    short       iValue;
    long        lValue;
    double      dValue;
};

int main()
{
    union NumericType Values = { 10 };   // iValue = 10
    cout << Values.iValue << endl;
    Values.dValue = 3.1416;
    cout << Values.dValue << endl;
}
/* Output:
10
3.141600
*/

NumericTypeunion se organiza en memoria (conceptualmente) como se muestra en la ilustración siguiente:

Diagram that shows the overlapping storage of data in the NumericType union.

El diagrama muestra 8 bytes de datos. El tipo doble dValue ocupa los 8 bytes completos. El tipo long lValue ocupa los primeros 4 bytes. El tipo corto iValue ocupa el primer byte.

union anónima

Una union anónima es una declarada sin un class-name o declarator-list.

union { member-list }

Los nombres declarados en una union anónima se usan directamente, como las variables no miembro. Implica que los nombres declarados en una union anónima deben ser únicos en el ámbito circundante.

Un anónimo union está sujeto a estas restricciones:

  • Si se declaran en el ámbito de archivo o espacio de nombres, se debe declarar como static.
  • Solo puede tener miembros public; el tener miembros private y protected en una union anónima genera errores.
  • No puede tener funciones miembro.

Consulte también

Clases y structs
Palabras clave
class
struct