union

Примечание.

В C++17 и более поздних версиях std::variantclass это типобезопасная альтернатива для union.

Это union определяемый пользователем тип, в котором все члены совместно используют одно расположение памяти. Это определение означает, union что в любое время может содержать не более одного объекта из списка элементов. Это также означает, что независимо от количества элементов union всегда используется достаточно памяти для хранения самого большого элемента.

Это union может быть полезно для экономии памяти при наличии большого количества объектов и ограниченной памяти. Тем не менее, union требуется дополнительная помощь, чтобы правильно использовать. Вы несете ответственность за обеспечение постоянного доступа к одному и тому же участнику, которому вы назначены. Если какие-либо типы элементов имеют нетривиальный конstructили, необходимо написать код, чтобы явным образом иstruct уничтожить этот элемент. Прежде чем использовать union, рассмотрите ли проблему, которую вы пытаетесь решить, можно лучше выразить с помощью базовых class и производных class типов.

Синтаксис

uniontagнеоб.{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 имяstructInputотсутствует, поэтому он называется анонимнымunion. Доступ к его членам можно получить напрямую, как если бы они являются членами .struct Дополнительные сведения об использовании анонимного unionсм. в разделе "Анонимныйunion".

В предыдущем примере показана проблема, которую также можно решить с помощью class типов, производных от общей базы class. Вы можете ветвить код на основе типа среды выполнения каждого объекта в контейнере. Ваш код может быть проще поддерживать и понимать, но он также может быть медленнее, чем использование union. Кроме того, с unionпомощью приложения можно хранить несвязанные типы. Позволяет union динамически изменять тип хранимого значения, не изменяя тип самой переменной union . Например, можно создать разнородный массив MyUnionType, элементы которого хранят разные значения разных типов.

Это легко неправильно использовать Inputstruct в примере. Это до пользователя, чтобы использовать дискриминатор правильно для доступа к члену, в котором хранятся данные. Вы можете защититься от неправильного unionprivate использования, сделав и предоставив специальные функции доступа, как показано в следующем примере.

Неограниченный union (C++11)

В C++03 и более ранних версиях union может содержатьstatic не члены данных, имеющие class тип, если у типа нет предоставленных пользователем constructors, destructors или операторов назначения. В C++11 эти ограничения отсутствуют. Если в него включен такой членunion, компилятор автоматически помечает какие-либо специальные функции-члены, которые не предоставляются пользователем.deleted union Если он является анонимным union внутри class или struct, то любые специальные функции-члены classstruct или не предоставленные пользователем помечены как 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
*/

Он NumericTypeunion упорядочен в памяти (концептуально), как показано на следующем рисунке:

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

На схеме показаны 8 байт данных. Двойной тип dValue занимает весь 8 байт. Тип long lValue занимает первые 4 байта. Короткий тип iValue занимает первый байт.

Анонимные union

Анонимный union объявлен без class-name или declarator-list.

union { member-list }

Имена, объявленные в анонимном, union используются напрямую, например переменные nonmember. Это означает, что имена, объявленные в анонимном, union должны быть уникальными в окружающем область.

Анонимный union имеет следующие ограничения:

  • Если он объявлен в файле или пространстве имен область, он также должен быть объявлен как static.
  • У него могут быть только public члены; наличие private и protected члены в анонимном union генерации ошибок.
  • Он не может иметь функции-члены.

См. также

Классы и структуры
Ключевые слова
class
struct