Delen via


union

Opmerking

In C++17 en hoger is het std::variantclass een typeveilig alternatief voor een union.

Een union is een door de gebruiker gedefinieerd type waarin alle leden dezelfde geheugenlocatie delen. Deze definitie betekent dat een union object op elk gewenst moment niet meer dan één object uit de lijst met leden mag bevatten. Het betekent ook dat ongeacht het aantal leden dat een union heeft, het altijd slechts voldoende geheugen gebruikt om het grootste lid op te slaan.

Een union kan handig zijn voor het besparen van geheugen wanneer u veel objecten en beperkt geheugen hebt. union Er is echter extra zorg nodig om correct te gebruiken. U bent verantwoordelijk om ervoor te zorgen dat u altijd toegang hebt tot hetzelfde lid dat u hebt toegewezen. Als een lidtype een niet-triviale constructor heeft, moet u code schrijven om dat lid expliciet te maken en te vernietigen. Voordat u een unionoplossing gebruikt, moet u overwegen of het probleem dat u probeert op te lossen beter kan worden uitgedrukt met behulp van een basis class - en afgeleide class typen.

Syntaxis

union tag opteren{member-list};

Parameterwaarden

tag
De typenaam die aan de union.

member-list
Leden die het union kan bevatten.

Declareer een union

Begin de declaratie van een union met behulp van het union trefwoord en plaats de ledenlijst in accolades:

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

Een union

In het vorige voorbeeld moet elke code die toegang heeft tot de union behoeften weten welk lid de gegevens bevat. De meest voorkomende oplossing voor dit probleem wordt een gediscrimineerd unionprobleem genoemd. Het plaatst de union in een struct, en bevat een enum lid dat het lidtype aangeeft dat momenteel is opgeslagen in de union. In het volgende voorbeeld ziet u het basispatroon:

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

In het vorige voorbeeld heeft het union in het Inputstruct vorige voorbeeld geen naam, dus het wordt een anoniemeunion naam genoemd. De leden kunnen rechtstreeks worden geopend alsof ze lid zijn van de struct. Zie de sectie Anoniem union voor meer informatie over het gebruik van een anonieme unionfunctie.

In het vorige voorbeeld ziet u een probleem dat u ook kunt oplossen met behulp van class typen die zijn afgeleid van een gemeenschappelijke basis class. U kunt uw code vertakken op basis van het runtimetype van elk object in de container. Uw code is misschien gemakkelijker te onderhouden en te begrijpen, maar het kan ook langzamer zijn dan het gebruik van een union. Met een unionkunt u ook niet-gerelateerde typen opslaan. Met A union kunt u het type van de opgeslagen waarde dynamisch wijzigen zonder het type van de union variabele zelf te wijzigen. U kunt bijvoorbeeld een heterogene matrix van MyUnionType, waarvan de elementen verschillende waarden van verschillende typen opslaan.

Het is eenvoudig om het Inputstruct in het voorbeeld te misbruiken. Het is aan de gebruiker om de discriminator correct te gebruiken voor toegang tot het lid dat de gegevens bevat. U kunt bescherming bieden tegen misbruik door speciale unionprivate toegangsfuncties te maken, zoals wordt weergegeven in het volgende voorbeeld.

union Onbeperkt (C++11)

In C++03 en eerder kan een union niet-statische gegevensleden bevatten die een class type hebben, zolang het type geen door de gebruiker geleverde constructors, destructors of toewijzingsoperatoren heeft. In C++11 worden deze beperkingen verwijderd. Als u een dergelijk lid in uw unionopneemt, markeert de compiler automatisch eventuele speciale lidfuncties die niet door de gebruiker zijn opgegeven als deleted. Als de union functie anoniem union is binnen een class ofstruct, worden speciale lidfuncties van de of struct die niet door de class gebruiker zijn opgegeven gemarkeerd als deleted. In het volgende voorbeeld ziet u hoe u deze case kunt afhandelen. Een van de leden van de union groep heeft een lid dat deze speciale behandeling vereist:

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

Een union verwijzing kan niet worden opgeslagen. Een union biedt ook geen ondersteuning voor overname. Dit betekent dat u geen als union basis classkunt gebruiken of overnemen van een andere class, of virtuele functies kunt hebben.

Initialiseer een union

U kunt een union in dezelfde instructie declareren en initialiseren door een expressie tussen accolades toe te wijzen. De expressie wordt geëvalueerd en toegewezen aan het eerste veld van de 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
*/

Het NumericTypeunion is gerangschikt in het geheugen (conceptueel) zoals wordt weergegeven in de volgende afbeelding:

Diagram met de overlappende opslag van gegevens in het Numerieketype union.

In het diagram ziet u 8 bytes aan gegevens. Het dubbele type dValue neemt de volledige 8 bytes in beslag. Het type lange lValue neemt de eerste 4 bytes in beslag. Het korte type iValue neemt de eerste byte in beslag.

Anoniem union

Een anonieme union is een gedeclareerd zonder een class-name of declarator-list.

union { member-list }

Namen die in een anonieme union worden gedeclareerd, worden rechtstreeks gebruikt, zoals niet-lidvariabelen. Het impliceert dat de namen die zijn gedeclareerd in een anonieme union naam uniek moeten zijn in het omringende bereik.

Anoniem union is onderhevig aan deze beperkingen:

  • Als deze is gedeclareerd in het bereik van een bestand of naamruimte, moet deze ook worden gedeclareerd als static.
  • Het kan alleen public leden hebben; het hebben private en protected leden in een anonieme union genereert fouten.
  • Het kan geen lidfuncties hebben.

Zie ook

Klassen en Structs
Zoekwoorden
class
struct