union
Observação
Em C++17 e posteriores, std::variant
class é 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
union
tag
opt{
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 Input
struct 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 Input
struct 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 NumericType
union está organizado na memória (conceitualmente) como mostrado na figura a seguir:
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 membrosprivate
eprotected
em uma union anônima gera erros. - Ela não pode ter funções de membro.
Confira também
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de