Partager via


union

Remarque

Dans C++17 et ultérieur, la classe std::variant est une alternative de type sécurisé pour une union.

Une union est un type défini par l’utilisateur dans lequel tous les membres partagent le même emplacement mémoire. Cette définition signifie qu’à un moment donné, une union ne peut pas contenir plusieurs objets de sa liste de membres. Cela signifie également que, quel que soit le nombre de membres d’une union, elle utilise toujours seulement la quantité de mémoire nécessaire pour stocker le membre le plus grand.

Une union peut être utile pour économiser de la mémoire quand vous avez un grand nombre d’objets et une mémoire limitée. Cependant, une utilisation correcte de union nécessite une attention particulière. Vous avez la responsabilité de vérifier que vous accédez toujours au même membre que celui que vous avez affecté. Si des types membres ont un constructeur non trivial, vous devez écrire du code pour construire et détruire explicitement ce membre. Avant d’utiliser une union, déterminez si le problème que vous essayez de résoudre pourrait être mieux exprimé en utilisant une classe de base et des types de classes dérivées.

Syntaxe

union tagopt { member-list };

Paramètres

tag
Le nom du type donné à l’union.

member-list
Membres que l’union peut contenir.

Déclarer une union

Commencez la déclaration d’une union en utilisant le mot clé union et placez la liste des membres entre 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 une union

Dans l’exemple précédent, le code qui accède à l’union doit connaître le membre qui contient les données. La solution la plus courante à ce problème est appelée une union discriminée. Il place l’union dans un struct et inclut un membre enum qui indique le type de membre actuellement stocké dans l’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’union dans le struct Input n’a pas de nom : elle est donc appelée union anonyme. Ses membres sont accessibles directement comme s’ils étaient membres du struct. Pour plus d’informations sur l’utilisation d’une union anonyme, consultez la section union anonyme.

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

Il est facile de mal utiliser le struct Input de l’exemple. Il revient à l’utilisateur d’utiliser le discriminateur correctement pour accéder au membre qui contient les données. Vous pouvez vous protéger contre une mauvaise utilisation en rendant l’union privée (private) et en fournissant des fonctions d’accès spéciales, comme indiqué dans l’exemple suivant.

union non restreinte (C++11)

En C++03 et versions antérieures, un union peut contenir des membres de données non statiques ayant un type class, tant que le type n’a pas de constructeurs, destructeurs ou opérateurs d’affectation fournis par l’utilisateur. En C++11, ces restrictions sont supprimées. Si vous incluez un tel membre dans votre union, le compilateur marque automatiquement les fonctions membres spéciales qui ne sont pas fournies par l’utilisateur comme deleted. Si l’union est une union anonyme à l’intérieur d’une classe ou d’un struct, les fonctions membres spéciales de la classe ou du struct qui ne sont pas fournies par l’utilisateur sont marquées comme deleted. L’exemple suivant montre comment gérer ce cas. Un des membres de l’union a un membre qui nécessite 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;
}

Une union ne peut pas stocker une référence. Une union ne prend pas non plus en charge l’héritage. Cela signifie que vous ne pouvez pas utiliser une union en tant que classe de base, ni hériter d’une autre classe, ni avoir des fonctions virtuelles.

Initialiser une union

Vous pouvez déclarer et initialiser une 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 de l’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’union NumericType est organisée en mémoire (conceptuellement) comme illustré dans la figure suivante :

Diagramme montrant le chevauchement du stockage des données dans l’union NumericType.

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

union anonyme

Une union anonyme est déclarée sans class-name ni declarator-list.

union { member-list }

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

Une union anonyme est soumise à 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.
  • Elle peut avoir seulement des membres public ; avoir des membres private et protected dans une union anonyme génère des erreurs.
  • Elle ne peut pas avoir de fonctions membres.

Voir aussi

Classes et structs
Mots clés
class
struct