union

Observação

Em C++17 e posteriores, std::variantclass é uma alternativa fortemente tipada para um union.

Um union é um tipo definido pelo usuário no qual todos os membros compartilham o mesmo local de memória. Essa definição significa que, a qualquer momento, um union não pode conter mais de um objeto de sua lista de membros. Isso também significa que, não importa quantos membros union tenha, ele sempre usa apenas memória suficiente para armazenar o maior membro.

Um union pode ser útil para conservar memória quando você tem muitos objetos e memória limitada. No entanto, union requer cuidado extra para ser usado corretamente. Você é responsável por garantir que sempre acesse o mesmo membro atribuído. Se qualquer tipo de membro tiver um con não trivial ou, então você deve escrever código para explicitamente constructstruct e destruir esse membro. Antes de usar uma union, considere se o problema que você está tentando resolver poderia ser melhor expresso usando uma class base e tipos de class derivados.

Sintaxe

uniontagopt{member-list};

Parâmetros

tag
O nome do tipo dado à union.

member-list
Membros que union pode conter.

Declarar um union

Inicie a declaração de uma union com a palavra-chave union e coloque a lista de membros entre chaves:

// 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 um union

No exemplo anterior, qualquer código que acesse union precisa saber qual membro contém os dados. A solução mais comum para esse problema é chamada de union discriminada. Ela inclui uma union em um struct e inclui um membro enum que indica o tipo de membro atualmente armazenado na union. O exemplo a seguir mostra o padrão 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;
}

No exemplo anterior, a union no Inputstruct não tem nome, portanto, é chamada de union anônima. Seus membros podem ser acessados diretamente como se fossem membros do struct. Para mais informações sobre como usar um anônimo union, confira a seção Anônimo union.

O exemplo anterior mostra um problema que você também pode resolver usando tipos de class que derivam de uma class base comum. Você pode ramificar seu código com base no tipo de runtime de cada objeto no contêiner. Seu código pode ser mais fácil de manter e entender, mas também pode ser mais lento do que usar uma union. Além disso, com uma union, você pode armazenar tipos não relacionados. A union permite alterar dinamicamente o tipo do valor armazenado sem alterar o tipo da variável union em si. Por exemplo, você pode criar uma matriz heterogênea de MyUnionType, cujos elementos armazenam valores diferentes de diferentes tipos.

É fácil usar indevidamente o Inputstruct no exemplo. Cabe ao usuário usar o discriminador corretamente para acessar o membro que contém os dados. Você pode proteger contra o uso indevido, criando a unionprivate fornecendo funções de acesso especiais, conforme mostrado no próximo exemplo.

union irrestrita (C++11)

Em C++03 e versões anteriores, um pode conter membros que não são de dados que têm um unionclass tipo, desde que o tipo nãostatic tenha constructors, de ors ou operadores destructatribuição fornecidos pelo usuário. No C++11, essas restrições são removidas. Se você incluir um membro desse tipo na sua union, o compilador marcará automaticamente as funções de membro especiais que não são fornecidas pelo usuário como deleted. Se a union for uma union anônima dentro de uma class ou de um struct, qualquer função de membro especial da class ou do struct que não seja fornecida pelo usuário será marcada como deleted. O exemplo a seguir mostra como lidar com esse caso. Um dos membros da union tem um membro que requer este tratamento 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;
}

Uma union não pode armazenar uma referência. A union também não suporta herança. Isso significa que você não pode usar uma union base class nem herdar de outra class ou ter funções virtuais.

Inicializar um union

Você pode declarar e inicializar uma union na mesma instrução atribuindo uma expressão entre chaves. A expressão é avaliada e atribuída ao primeiro campo da 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
*/

O NumericTypeunion está organizado na memória (conceitualmente) como mostrado na figura a seguir:

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

O diagrama mostra 8 bytes de dados. O tipo duplo dValue ocupa os 8 bytes inteiros. O tipo long lValue ocupa os primeiros 4 bytes. O tipo curto iValue ocupa o primeiro byte.

union anônimo

Uma union anônima é um declarada sem um class-name ou declarator-list.

union { member-list }

Os nomes declarados em uma union anônima são usados diretamente, como variáveis de não membro. Isso implica que os nomes declarados em uma union anônima devem ser exclusivos no escopo ao redor.

Um anônimo union está sujeito a estas restrições:

  • Se declarada no escopo de arquivo ou namespace, também deve ser declarada como static.
  • Ela pode ter apenas membros public; ter membros private e protected em uma union anônima gera erros.
  • Ela não pode ter funções de membro.

Confira também

Classes e Structs
Palavras-chave
class
struct