union

Remarque

En C++17 et versions ultérieures, il std::variantclass s’agit d’une alternative de type safe pour un union.

Il union s’agit d’un type défini par l’utilisateur dans lequel tous les membres partagent le même emplacement de mémoire. Cette définition signifie qu’à tout moment, un union objet ne peut contenir plus d’un objet de sa liste de membres. Cela signifie également que, quel que soit le nombre de membres qu’un union membre a, il utilise toujours suffisamment de mémoire pour stocker le plus grand membre.

Un union peut être utile pour conserver la mémoire lorsque vous avez un grand nombre d’objets et une mémoire limitée. Toutefois, un union soin supplémentaire doit être utilisé correctement. Vous êtes responsable de vous assurer que vous accédez toujours au même membre que celui que vous avez affecté. Si des types de membres ont un constructcon ou non, vous devez écrire du code pour construct et détruire explicitement ce membre. Avant d’utiliser un union, déterminez si le problème que vous essayez de résoudre peut être mieux exprimé à l’aide d’un type de base class et dérivé class .

Syntaxe

uniontagopt{member-list};

Paramètres

tag
Nom de type donné au union.

member-list
Membres que le union conteneur peut contenir.

Déclarer un union

Commencez la déclaration d’un union à l’aide du union mot clé et placez la liste des membres dans des 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
}

Utiliser un union

Dans l’exemple précédent, tout code qui accède aux union besoins de savoir quel membre contient les données. La solution la plus courante à ce problème est appelée discrimination union. Il place le union contenu dans un structmembre qui enum indique le type de membre actuellement stocké dans le union. L'exemple suivant illustre le modèle de 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;
}

Dans l’exemple précédent, l’élément union dans le Inputstruct fichier n’a pas de nom. Il est donc appelé anonymeunion. Ses membres sont accessibles directement comme s’ils sont membres du struct. Pour plus d’informations sur l’utilisation d’un anonyme union, consultez la section Anonyme union .

L’exemple précédent montre un problème que vous pouvez également résoudre à l’aide class de types qui dérivent d’une base classcommune. Vous pouvez brancher votre code en fonction du type d’exécution de chaque objet dans le conteneur. Votre code peut être plus facile à gérer et à comprendre, mais il peut également être plus lent que d’utiliser un union. En outre, avec un union, vous pouvez stocker des types non liés. A union vous permet de modifier dynamiquement le type de la valeur stockée sans modifier le type de la union variable elle-même. Par exemple, vous pouvez créer un tableau hétérogène de MyUnionType, dont les éléments stockent différentes valeurs de différents types.

Il est facile de mal utiliser l’exemple Inputstruct . Il appartient à l’utilisateur d’utiliser correctement le discriminateur pour accéder au membre qui contient les données. Vous pouvez vous protéger contre toute utilisation abusive en rendant les unionprivate fonctions d’accès spéciales et en fournissant des fonctions d’accès spéciales, comme illustré dans l’exemple suivant.

Illimité union (C++11)

Dans C++03 et versions antérieures, un union peut contenir des membres nonstatic de données qui ont un class type, tant que le type n’a pas d’utilisateur fourni de constructors, destructors ou d’opérateurs d’affectation. En C++11, ces restrictions sont supprimées. Si vous incluez un tel membre dans votre union, le compilateur marque automatiquement toutes les fonctions de membre spéciales qui ne sont pas fournies par l’utilisateur en tant que deleted. union S’il s’agit d’un anonyme union à l’intérieur d’un class ou struct, toutes les fonctions membres spéciales du class ou struct qui ne sont pas fournies par l’utilisateur sont marquées comme deleted. L’exemple suivant montre comment gérer ce cas. L’un des membres du union groupe a un membre qui exige ce traitement spécial :

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

Impossible union de stocker une référence. Un union autre élément ne prend pas en charge l’héritage. Cela signifie que vous ne pouvez pas utiliser une union base class, ou hériter d’une autre classou avoir des fonctions virtuelles.

Initialiser un union

Vous pouvez déclarer et initialiser un union dans la même instruction en affectant une expression placée entre accolades. L’expression est évaluée et affectée au premier champ du 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’élément NumericTypeunion est organisé en mémoire (conceptuellement), comme illustré dans la figure suivante :

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

Le diagramme montre 8 octets de données. Le double type dValue occupe les 8 octets entiers. Le type lValue long occupe les 4 premiers octets. Le type court iValue occupe le premier octet.

Anonyme union

Un anonyme union est déclaré sans ou class-namedeclarator-list.

union { member-list }

Les noms déclarés dans un anonyme union sont utilisés directement, comme les variables non membres. Cela implique que les noms déclarés dans un anonyme union doivent être uniques dans l’étendue environnante.

Un anonyme union est soumis à ces restrictions :

  • Si elle est déclarée dans l’étendue du fichier ou de l’espace de noms, elle doit également être déclarée en tant que static.
  • Il ne peut avoir que public des membres ; avoir private et protected des membres dans un anonyme union génère des erreurs.
  • Il ne peut pas avoir de fonctions membres.

Voir aussi

Classes et structs
Mots clés
class
struct