union

Nota

In C++17 e versioni successive, è un'alternativa std::variantclass indipendente dai tipi per un oggetto union.

Un union è un tipo definito dall'utente in cui tutti i membri condividono la stessa posizione di memoria. Questa definizione significa che in un determinato momento un union oggetto non può contenere più di un oggetto dal relativo elenco di membri. Significa anche che, indipendentemente dal numero di membri di un union oggetto, usa sempre memoria sufficiente per archiviare il membro più grande.

Un union può essere utile per conservare la memoria quando si dispone di un sacco di oggetti e memoria limitata. Tuttavia, un richiede union maggiore attenzione per l'uso corretto. L'utente è responsabile di assicurarsi di accedere sempre allo stesso membro assegnato. Se i tipi di membro hanno un constructnontrivial o , è necessario scrivere codice in modo esplicito estruct distruggere tale membro. Prima di usare un unionoggetto , valutare se il problema che si sta tentando di risolvere può essere espresso meglio usando un tipo di base class e derivato class .

Sintassi

uniontagopt{member-list};

Parametri

tag
Nome del tipo assegnato all'oggetto union.

member-list
Membri che l'oggetto union può contenere.

Dichiarare un oggetto union

Iniziare la dichiarazione di un union oggetto usando la union parola chiave e racchiudere l'elenco dei membri tra parentesi graffe:

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

Usare un oggetto union

Nell'esempio precedente, qualsiasi codice che accede union a deve conoscere il membro che contiene i dati. La soluzione più comune a questo problema è detta discriminanteunion. Racchiude in union e include un enum membro che indica il tipo di membro attualmente archiviato in unionstruct. 'esempio seguente mostra il modello di base:

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

Nell'esempio precedente, union in in Inputstruct non ha alcun nome, quindi viene chiamato anonimounion. I relativi membri possono essere accessibili direttamente come se fossero membri di struct. Per altre informazioni su come usare un anonimo union, vedere la sezione Anonima union .

Nell'esempio precedente viene illustrato un problema che è possibile risolvere anche usando class i tipi che derivano da una base classcomune. È possibile creare un ramo del codice in base al tipo di runtime di ogni oggetto nel contenitore. Il codice potrebbe essere più semplice da gestire e comprendere, ma potrebbe anche essere più lento rispetto all'uso di un oggetto union. Inoltre, con un unionoggetto è possibile archiviare tipi non correlati. Un union consente di modificare dinamicamente il tipo del valore archiviato senza modificare il tipo della union variabile stessa. Ad esempio, è possibile creare una matrice eterogenea di MyUnionType, i cui elementi archiviano valori diversi di tipi diversi.

È facile usare in modo improprio nell'esempio Inputstruct . Spetta all'utente usare correttamente il discriminatorio per accedere al membro che contiene i dati. È possibile proteggersi da un uso improprio rendendo unionprivate e fornendo funzioni di accesso speciali, come illustrato nell'esempio seguente.

Senza restrizioni union (C++11)

In C++03 e versioni precedenti, un union oggetto può contenere membri nonstatic dati con un class tipo, purché il tipo non disponga di operatori di assegnazione, destructors o constructos forniti dall'utente. In C++11 queste restrizioni sono state rimosse. Se si include tale membro nel , unionil compilatore contrassegna automaticamente tutte le funzioni membro speciali che non sono fornite dall'utente come deleted. union Se è un anonimo union all'interno di o structclass , tutte le class funzioni membro speciali di o struct che non sono fornite dall'utente vengono contrassegnate come deleted. Nell'esempio seguente viene illustrato come gestire questo caso. Uno dei membri dell'ha union un membro che richiede questo trattamento speciale:

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

Un union oggetto non può archiviare un riferimento. Un union oggetto non supporta anche l'ereditarietà. Ciò significa che non è possibile usare come union base classo ereditare da un altro classoggetto o avere funzioni virtuali.

Inizializzare un oggetto union

È possibile dichiarare e inizializzare un oggetto union nella stessa istruzione assegnando un'espressione racchiusa tra parentesi graffe. L'espressione viene valutata e assegnata al primo campo dell'oggetto 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
*/

L'oggetto NumericTypeunion è disposto in memoria (concettualmente), come illustrato nella figura seguente:

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

Il diagramma mostra 8 byte di dati. Il tipo double dValue occupa l'intero 8 byte. Il tipo long lValue occupa i primi 4 byte. Il tipo breve iValue occupa il primo byte.

Anonimo union

Un anonimo union è dichiarato senza o class-namedeclarator-list.

union { member-list }

I nomi dichiarati in un anonimo union vengono usati direttamente, come le variabili non membro. Implica che i nomi dichiarati in un anonimo union devono essere univoci nell'ambito circostante.

Un anonimo union è soggetto a queste restrizioni:

  • Se dichiarato nell'ambito del file o dello spazio dei nomi, deve anche essere dichiarato come static.
  • Può avere solo public membri. La presenza private di membri e protected in un anonimo union genera errori.
  • Non può avere funzioni membro.

Vedi anche

Classi e struct
Parole chiave
class
struct