union

Uwaga

W języku C++17 lub nowszym std::variantclass element jest bezpieczną alternatywą dla klasy union.

A union to typ zdefiniowany przez użytkownika, w którym wszyscy członkowie mają tę samą lokalizację pamięci. Ta definicja oznacza, że w danym momencie obiekt union może zawierać nie więcej niż jeden obiekt z listy elementów członkowskich. Oznacza to również, że niezależnie od liczby elementów członkowskich union , zawsze używa wystarczającej ilości pamięci do przechowywania największego elementu członkowskiego.

Może union być przydatne do oszczędzania pamięci, gdy masz wiele obiektów i ograniczoną pamięć. Jednak wymaga dodatkowej union opieki, aby prawidłowo używać. Odpowiadasz za zapewnienie, że zawsze uzyskujesz dostęp do tego samego przypisanego członka. Jeśli jakiekolwiek typy elementów członkowskich mają nietrivial constructlub, musisz napisać kod jawnie construct i zniszczyć ten element członkowski. Przed użyciem unionelementu należy rozważyć, czy problem, który próbujesz rozwiązać, może być lepiej wyrażony przy użyciu typów bazowych class i pochodnych class .

Składnia

uniontagZdecydować{member-list};

Parametry

tag
Nazwa typu nadana elementowi union.

member-list
Elementy członkowskie, które union mogą zawierać.

Deklarowanie elementu union

Rozpocznij deklarację elementu union za pomocą słowa kluczowego union i dołącz listę elementów członkowskich w nawiasach klamrowych:

// 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
}

Korzystanie z union

W poprzednim przykładzie każdy kod, który uzyskuje union dostęp do elementu, musi wiedzieć, który element członkowski przechowuje dane. Najczęstszym rozwiązaniem tego problemu jest dyskryminowana union. Otacza element union w obiekcie structi zawiera element enum członkowski wskazujący typ elementu członkowskiego, który jest obecnie przechowywany w elemencie union. W poniższym przykładzie przedstawiono podstawowy wzorzec:

#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;
}

W poprzednim przykładzie element union w elemeciestructInputnie ma nazwy, więc jest nazywany anonimowymunion. Dostęp do jego członków można uzyskać bezpośrednio tak, jakby byli członkami .struct Aby uzyskać więcej informacji na temat używania anonimowego unionelementu , zobacz sekcję Anonimowe union .

W poprzednim przykładzie przedstawiono problem, który można również rozwiązać przy użyciu class typów pochodzących z typowej bazy class. Kod można rozgałęzić na podstawie typu środowiska uruchomieniowego każdego obiektu w kontenerze. Twój kod może być łatwiejszy do utrzymania i zrozumienia, ale może być również wolniejszy niż użycie elementu union. Ponadto za pomocą elementu unionmożna przechowywać niepowiązane typy. Element A union umożliwia dynamiczną zmianę typu przechowywanej wartości bez zmiany typu samej zmiennej union . Można na przykład utworzyć heterogeniczną tablicę MyUnionType, której elementy przechowują różne wartości różnych typów.

W tym przykładzie łatwo jest źle wykorzystać Inputstruct ten kod. Do użytkownika należy użycie dyskryminującego poprawnie dostępu do elementu członkowskiego, który przechowuje dane. Ochronę przed nieprawidłowym użyciem można chronić, tworząc unionprivate i zapewniając specjalne funkcje dostępu, jak pokazano w następnym przykładzie.

union Nieograniczone (C++11)

W języku C++03 lub starszym union element może zawierać niezwiązanestatic z danymi elementy członkowskie, które mają class typ, o ile typ nie ma podanego przez użytkownika operatora constructlub destructlub operatorów przypisania. W języku C++11 te ograniczenia są usuwane. Jeśli uwzględnisz taki element członkowski w elemencie union, kompilator automatycznie oznaczy wszystkie specjalne funkcje członkowskie, które nie są podane przez użytkownika jako deleted. union Jeśli element jest anonimowy union wewnątrz elementu class lub struct, wszystkie specjalne funkcje class członkowskie elementu lubstruct, które nie są podane przez użytkownika, są oznaczone jako deleted. W poniższym przykładzie pokazano, jak obsłużyć ten przypadek. Jeden z członków programu union ma członka, który wymaga tego specjalnego traktowania:

// 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;
}

Nie union można zapisać odwołania. Element A union również nie obsługuje dziedziczenia. Oznacza to, że nie można używać elementu union jako podstawowego classlub dziedziczyć z innego classelementu lub mieć funkcji wirtualnych.

Inicjowanie obiektu union

Można zadeklarować i zainicjować element union w tej samej instrukcji, przypisując wyrażenie ujęte w nawiasy klamrowe. Wyrażenie jest oceniane i przypisywane do pierwszego pola obiektu 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
*/

Element NumericTypeunion jest rozmieszczony w pamięci (koncepcyjnie), jak pokazano na poniższej ilustracji:

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

Na diagramie przedstawiono 8 bajtów danych. Dwubajtowy typ dValue zajmuje całe 8 bajtów. Typ long lValue zajmuje pierwsze 4 bajty. Typ krótki iValue zajmuje pierwszy bajt.

Anonimowy union

Anonimowy jest jeden zadeklarowany union bez elementu class-name lub declarator-list.

union { member-list }

Nazwy zadeklarowane w anonimowym union są używane bezpośrednio, na przykład zmienne inne niż. Oznacza to, że nazwy zadeklarowane w anonimowym union zakresie muszą być unikatowe w otaczającym zakresie.

Anonimowy union obiekt podlega następującym ograniczeniom:

  • W przypadku zadeklarowania w zakresie pliku lub przestrzeni nazw należy go również zadeklarować jako static.
  • Może mieć tylko public członków; posiadanie private i protected członków w anonimowym union generowaniu błędów.
  • Nie może mieć funkcji składowych.

Zobacz też

Klasy i struktury
Słowa kluczowe
class
struct