union
Примечание
В C++17 и более поздних версиях std::variant
class является типобезопасной альтернативой unionдля .
— union
это определяемый пользователем тип, в котором все участники имеют одно расположение в памяти. Это определение означает, что в любой union момент времени объект может содержать не более одного объекта из своего списка элементов. Это также означает, что независимо от того, сколько элементов union имеет , он всегда использует только достаточно памяти для хранения самого большого элемента.
Может union быть полезен для экономии памяти при наличии большого количества объектов и ограниченной памяти. Однако для правильного union использования требуется дополнительная осторожность. Вы несете ответственность за обеспечение постоянного доступа к одному и тому же назначенному участнику. Если какие-либо типы элементов имеют нетривиальный constructили , необходимо написать код для явного construct и уничтожить этот элемент. Прежде чем использовать union, подумайте, можно ли лучше выразить проблему с помощью базовых class и производных class типов.
Синтаксис
union
tag
opt{
member-list
};
Параметры
tag
Имя типа, присвоенное объекту union.
member-list
Элементы, которые union может содержать объект .
Объявление union
Начните объявление union с помощью union
ключевое слово и заключите список элементов в фигурные скобки:
// 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
}
Использование union
В предыдущем примере любой код, который обращается к union , должен знать, какой член содержит данные. Наиболее распространенным решением этой проблемы является различаемое union. Он заключает union в structи включает элемент, указывающий enum тип элемента, который в настоящее время хранится в union. В следующем примере демонстрируется использование основного подхода:
#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;
}
В предыдущем примере union объект в Input
struct не имеет имени, поэтому называется анонимнымunion. Доступ к его членам можно получить напрямую, как если бы они были членами struct. Дополнительные сведения об использовании анонимного unionсм. в разделе Анонимные union .
В предыдущем примере показана проблема, которую можно также решить с помощью class типов, производных от общего базового .class Вы можете ветвить код на основе типа среды выполнения каждого объекта в контейнере. Ваш код может быть проще поддерживать и понимать, но он также может быть медленнее, чем использование union. Кроме того, с помощью unionможно хранить несвязанные типы. Позволяет union динамически изменять тип хранимого значения, не изменяя тип самой переменной union . Например, можно создать разнородный массив MyUnionType
, элементы которого хранят разные значения разных типов.
В примере легко использовать .Input
struct Пользователь должен правильно использовать дискриминатор для доступа к члену, который содержит данные. Вы можете защититься от неправильного unionprivate
использования, создав и предоставив специальные функции доступа, как показано в следующем примере.
Неограниченный union (C++11)
В C++03 и более ранних версиях может содержать элементы, union неstatic относящиеся к данным, с типом class , если в типе не указаны операторы constructors, destructors или присваивания. В C++11 эти ограничения отсутствуют. При включении такого элемента в unionкомпилятор автоматически помечает все специальные функции-члены, которые не предоставляются пользователем, как deleted
. union Если является анонимным union внутри class или struct, то любые специальные class функции-члены или struct , которые не предоставляются пользователем, помечаются как deleted
. В следующем примере показано, как обрабатывать этот случай. Один из членов union имеет член , который требует такого специального обращения:
// 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;
}
Не union может хранить ссылку. Также union не поддерживает наследование. Это означает, что вы не можете использовать union в качестве основы class, наследовать от другого classили иметь виртуальные функции.
Инициализация union
Можно объявить и инициализировать union в той же инструкции, назначив выражение, заключенное в фигурные скобки. Выражение вычисляется и присваивается первому полю 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
*/
Объект NumericType
union упорядочен в памяти (концептуально), как показано на следующем рисунке:
На схеме показаны 8 байт данных. Тип double dValue занимает все 8 байт. Тип long lValue занимает первые 4 байта. Короткий тип iValue занимает первый байт.
Анонимные union
Анонимный union — это объект, объявленный class-name
без или declarator-list
.
union {
member-list
}
Имена, объявленные в анонимном union объекте, используются напрямую, например переменные, не являющиеся вмеблером. Это означает, что имена, объявленные в анонимномunion, должны быть уникальными в окружающих область.
На анонимную union распространяются следующие ограничения:
- Если оно объявлено в файле или пространстве имен область, оно также должно быть объявлено как
static
. - У него могут быть только
public
члены; наличиеprivate
элементов иprotected
в анонимном union экземпляре приводит к ошибкам. - Он не может иметь функции-члены.