Sdílet prostřednictvím


union

Poznámka

V jazyce C++17 a novějším std::variantclass se jedná o typově bezpečnou alternativu pro .union

A union je uživatelem definovaný typ, ve kterém všichni členové sdílejí stejné umístění paměti. Tato definice znamená, že v daném okamžiku union nesmí obsahovat více než jeden objekt ze seznamu členů. Také to znamená, že bez ohledu na to, kolik členů union má, vždy používá pouze dostatek paměti k uložení největšího člena.

Může union být užitečné pro úsporu paměti, pokud máte velké množství objektů a omezenou paměť. Vyžaduje však union větší péči, aby se správně používala. Zodpovídáte za to, že budete mít vždy přístup ke stejnému členu, kterému jste přiřadili. Pokud některé typy členů mají nontriviální constructnebo pak musíte napsat kód explicitně construct a zničit tohoto člena. Než začnete používat union, zvažte, jestli by problém, který se pokoušíte vyřešit, lépe vyjádřit pomocí základního class a odvozeného class typu.

Syntaxe

uniontagRozhodnout{member-list};

Parametry

tag
Názevtypuho union

member-list
Členové, které union můžou obsahovat.

Deklarace union

Zahajte deklaraci klíčového unionunion slova a uzavřete seznam členů do složených závorek:

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

Použití union

V předchozím příkladu musí každý kód, který přistupuje k union datům, vědět, který člen obsahuje data. Nejběžnějším řešením tohoto problému je diskriminováno union. Uzavře do objektu unionstructa obsahuje enum člen, který označuje typ členu, který je aktuálně uložen v objektu union. Následující příklad ukazuje základní vzor:

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

V předchozím příkladu unionInputstruct neobsahuje žádné jméno, takže se nazývá anonymní.union K jeho členům lze přistupovat přímo, jako by byli členy struct. Další informace o použití anonymního unionsouboru naleznete v části Anonymní union .

Předchozí příklad ukazuje problém, který byste mohli vyřešit také pomocí class typů odvozených od společného základu class. Kód můžete rozvětvovat na základě typu modulu runtime každého objektu v kontejneru. Váš kód může být snazší udržovat a pochopit, ale může být také pomalejší než použití union. unionMůžete také ukládat nesouvisející typy. Funkce A union umožňuje dynamicky měnit typ uložené hodnoty beze změny typu union samotné proměnné. Můžete například vytvořit heterogenní pole MyUnionType, jehož prvky ukládají různé hodnoty různých typů.

V příkladu je snadné zneužít Inputstruct . Je na uživateli, aby pro přístup k členovi, který obsahuje data, správně používal diskriminátor. Můžete chránit před zneužitím tím, že vytvoříte unionprivate a poskytnete speciální přístupové funkce, jak je znázorněno v dalším příkladu.

union Neomezené (C++11)

V jazyce C++03 a starším union může obsahovat jinéstatic než datové členy, které mají typ class , pokud typ neobsahuje žádné operátory constructors, destructors nebo assignment. V jazyce C++11 jsou tato omezení odebrána. Pokud takový člen zahrnete do svého union, kompilátor automaticky označí všechny speciální členské funkce, které nejsou poskytovány jako deleted. Pokud je anonymní uvnitř nebo structclass , jsou všechny speciální členské funkce structclass poskytnuté uživatelem označeny jako deleted.unionunion Následující příklad ukazuje, jak tento případ zpracovat. Jeden ze členů union má člena, který vyžaduje toto zvláštní zacházení:

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

Odkaz union nelze uložit. A union také nepodporuje dědičnost. To znamená, že nemůžete použít union jako základ classnebo dědit z jiného classnebo mít virtuální funkce.

Inicializace union

Můžete deklarovat a inicializovat union ve stejném příkazu přiřazením výrazu uzavřeného do složených závorek. Výraz se vyhodnotí a přiřadí prvnímu unionpoli .

#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
*/

Je NumericTypeunion uspořádaný v paměti (koncepčně), jak je znázorněno na následujícím obrázku:

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

Diagram znázorňuje 8 bajtů dat. Dvojitý typ dValue zabírá celých 8 bajtů. Typ long lValue zabírá prvních 4 bajtů. Krátký typ iValue zabírá první bajt.

Anonymní union

Anonymní union je jeden deklarovaný bez nebo class-namedeclarator-list.

union { member-list }

Názvy deklarované v anonymním union objektu se používají přímo, například nečlenné proměnné. Z toho vyplývá, že názvy deklarované v anonymním union objektu musí být v okolním oboru jedinečné.

union Anonymní se řídí těmito omezeními:

  • Pokud je deklarován v oboru názvů nebo souboru, musí být deklarován také jako static.
  • Může obsahovat pouze public členy, kteří mají private a protected členové v anonymním objektu union generují chyby.
  • Nemůže mít členské funkce.

Viz také

Třídy a struktury
Klíčová slova
class
struct