Namespaces
Variants

Union declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Une union est un type de classe spécial qui ne peut contenir qu'un seul de ses membres de données non statiques à la fois.

Table des matières

Syntaxe

La spécification de classe pour une déclaration d'union est similaire à une déclaration class ou struct :

union attr class-head-name { member-specification }
attr - (since C++11) séquence facultative d'un nombre quelconque d' attributs
class-head-name - le nom de l'union en cours de définition. Peut être précédé facultativement par un nested-name-specifier (séquence de noms et d'opérateurs de résolution de portée, se terminant par un opérateur de résolution de portée). Le nom peut être omis, auquel cas l'union est anonyme
member-specification - liste des spécificateurs d'accès, des déclarations et définitions de fonctions membres et d'objets membres.

Une union peut avoir des fonctions membres (y compris les constructeurs et les destructeurs), mais pas de fonctions virtuelles.

Une union ne peut pas avoir de classes de base et ne peut pas être utilisée comme classe de base.

Au plus un membre variant peut avoir un initialiseur de membre par défaut .

(since C++11)

Une union ne peut pas avoir de membres de données non statiques de types référence.

Les unions ne peuvent pas contenir un membre de données non statique avec une fonction membre spéciale non triviale.

(jusqu'en C++11)

Si une union contient un membre de données non statique avec une fonction membre spéciale non triviale, la fonction membre spéciale correspondante de l'union peut être définie comme supprimée, consultez la page de la fonction membre spéciale correspondante pour plus de détails.

(depuis C++11)

Tout comme dans la struct déclaration, l'accès par défaut des membres dans une union est public .

Explication

L'union est au moins aussi grande que nécessaire pour contenir son plus grand membre de données, mais n'est généralement pas plus grande. Les autres membres de données sont destinés à être alloués dans les mêmes octets qu'une partie de ce plus grand membre. Les détails de cette allocation sont définis par l'implémentation, sauf que tous les membres de données non statiques ont la même adresse. C'est un comportement indéfini de lire à partir du membre de l'union qui n'a pas été écrit le plus récemment. De nombreux compilateurs implémentent, comme extension de langage non standard, la capacité de lire les membres inactifs d'une union.

#include <cstdint>
#include <iostream>
union S
{
    std::int32_t n;     // occupe 4 octets
    std::uint16_t s[2]; // occupe 4 octets
    std::uint8_t c;     // occupe 1 octet
};                      // l'union entière occupe 4 octets
int main()
{
    S s = {0x12345678}; // initialise le premier membre, s.n est maintenant le membre actif
    // À ce stade, lire s.s ou s.c est un comportement indéfini,
    // mais la plupart des compilateurs le définissent.
    std::cout << std::hex << "s.n = " << s.n << '\n';
    s.s[0] = 0x0011; // s.s est maintenant le membre actif
    // À ce stade, lire s.n ou s.c est un comportement indéfini,
    // mais la plupart des compilateurs le définissent.
    std::cout << "s.c est maintenant " << +s.c << '\n' // 11 ou 00, selon la plateforme
              << "s.n est maintenant " << s.n << '\n'; // 12340011 ou 00115678
}

Sortie possible :

s.n = 12345678
s.c est maintenant 0
s.n est maintenant 115678

Chaque membre est alloué comme s'il était le seul membre de la classe.

Si les membres d'une union sont des classes avec des constructeurs et destructeurs définis par l'utilisateur, pour changer le membre actif, un destructeur explicite et un placement new sont généralement nécessaires :

#include <iostream>
#include <string>
#include <vector>
union S
{
    std::string str;
    std::vector<int> vec;
    ~S() {} // needs to know which member is active, only possible in union-like class 
};          // the whole union occupies max(sizeof(string), sizeof(vector<int>))
int main()
{
    S s = {"Hello, world"};
    // at this point, reading from s.vec is undefined behavior
    std::cout << "s.str = " << s.str << '\n';
    s.str.~basic_string();
    new (&s.vec) std::vector<int>;
    // now, s.vec is the active member of the union
    s.vec.push_back(10);
    std::cout << s.vec.size() << '\n';
    s.vec.~vector();
}

Sortie :

s.str = Hello, world
1
(depuis C++11)

Si deux membres d'union sont des types standard-layout , il est bien défini d'examiner leur sous-séquence commune sur n'importe quel compilateur.

Durée de vie des membres

La durée de vie d'un membre d'union commence lorsque le membre est activé. Si un autre membre était précédemment actif, sa durée de vie se termine.

Lorsque le membre actif d'une union est modifié par une expression d'affectation de la forme E1 = E2 qui utilise soit l'opérateur d'affectation intégré soit un opérateur d'affectation trivial, pour chaque membre X de l'union qui apparaît dans les sous-expressions d'accès membre et d'indice de tableau de E1 qui n'est pas une classe avec des constructeurs par défaut non triviaux ou supprimés, si la modification de X aurait un comportement indéfini selon les règles d'alias de type, un objet du type de X est implicitement créé dans le stockage désigné ; aucune initialisation n'est effectuée et le début de sa durée de vie est séquencé après le calcul de la valeur des opérandes gauche et droit et avant l'affectation.

union A { int x; int y[4]; };
struct B { A a; };
union C { B b; int k; };
int f()
{
    C c;               // ne démarre la durée de vie d'aucun membre de l'union
    c.b.a.y[3] = 4;    // OK : "c.b.a.y[3]", nomme les membres d'union c.b et c.b.a.y ;
                       // Cela crée des objets pour contenir les membres d'union c.b et c.b.a.y
    return c.b.a.y[3]; // OK : c.b.a.y fait référence à l'objet nouvellement créé
}
struct X { const int a; int b; };
union Y { X x; int k; };
void g()
{
    Y y = {{1, 2}}; // OK, y.x est le membre actif de l'union
    int n = y.x.a;
    y.k = 4;   // OK : termine la durée de vie de y.x, y.k est le membre actif de l'union
    y.x.b = n; // comportement indéfini : y.x.b modifié en dehors de sa durée de vie,
               // "y.x.b" nomme y.x, mais le constructeur par défaut de X est supprimé,
               // donc la durée de vie du membre d'union y.x ne démarre pas implicitement
}

Trivial move constructor, move assignment operator, (since C++11) copy constructor et copy assignment operator des types union copient les représentations d'objet. Si la source et la destination ne sont pas le même objet, ces fonctions membres spéciales démarrent la durée de vie de chaque objet (sauf pour les objets qui ne sont ni des sous-objets de la destination ni de type à durée de vie implicite ) imbriqué dans la destination correspondant à celui imbriqué dans la source avant que la copie ne soit effectuée. Sinon, elles ne font rien. Deux objets union ont le même membre actif correspondant (le cas échéant) après la construction ou l'affectation via des fonctions spéciales triviales.

Unions anonymes

Une union anonyme est une définition d'union sans nom qui ne définit simultanément aucune variable (y compris les objets du type union, les références ou les pointeurs vers l'union).

union { spécification-des-membres } ;

Les unions anonymes ont des restrictions supplémentaires : elles ne peuvent pas avoir de fonctions membres, ne peuvent pas avoir de membres de données statiques, et tous leurs membres de données doivent être publics. Les seules déclarations autorisées sont les membres de données non statiques et static_assert les déclarations (depuis C++11) .

Les membres d'une union anonyme sont injectés dans la portée englobante (et ne doivent pas entrer en conflit avec les autres noms déclarés dans celle-ci).

int main()
{
    union
    {
        int a;
        const char* p;
    };
    a = 1;
    p = "Jennifer";
}
**Note:** Le code C++ n'a pas été traduit car il se trouve dans des balises `
` et contient des termes spécifiques au C++ qui doivent être préservés selon les instructions. Seul le texte environnant aurait été traduit s'il y en avait eu.

Les unions anonymes au niveau de l'espace de noms doivent être déclarées static sauf si elles apparaissent dans un espace de noms sans nom.

Classes de type union

Une classe de type union est soit une union, soit une classe (non-union) qui a au moins une union anonyme comme membre. Une classe de type union possède un ensemble de membres variants  :

  • les membres de données non statiques de ses unions anonymes membres ;
  • de plus, si la classe de type union est une union, ses membres de données non statiques qui ne sont pas des unions anonymes.

Les classes de type union peuvent être utilisées pour implémenter les unions étiquetées .

#include <iostream>
// S a un membre de données non statique (tag), trois membres énumérateurs (CHAR, INT, DOUBLE), 
// et trois membres variantes (c, i, d)
struct S
{
    enum{CHAR, INT, DOUBLE} tag;
    union
    {
        char c;
        int i;
        double d;
    };
};
void print_s(const S& s)
{
    switch(s.tag)
    {
        case S::CHAR: std::cout << s.c << '\n'; break;
        case S::INT: std::cout << s.i << '\n'; break;
        case S::DOUBLE: std::cout << s.d << '\n'; break;
    }
}
int main()
{
    S s = {S::CHAR, 'a'};
    print_s(s);
    s.tag = S::INT;
    s.i = 123;
    print_s(s);
}

Sortie :

a
123

La bibliothèque standard C++ inclut std::variant , qui peut remplacer de nombreuses utilisations d'unions et de classes similaires aux unions. L'exemple ci-dessus peut être réécrit comme suit

#include <iostream>
#include <variant>
int main()
{
    std::variant<char, int, double> s = 'a';
    std::visit([](auto x){ std::cout << x << '\n';}, s);
    s = 123;
    std::visit([](auto x){ std::cout << x << '\n';}, s);
}

Sortie :

a
123
(depuis C++17)

Mots-clés

union

Rapports de défauts

Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.

DR Applicable à Comportement publié Comportement corrigé
CWG 1940 C++11 les unions anonymes n'autorisaient que les membres de données non statiques static_assert également autorisé

Références

  • Norme C++23 (ISO/CEI 14882:2024) :
  • 11.5 Unions [class.union]
  • Norme C++20 (ISO/CEI 14882:2020) :
  • 11.5 Unions [class.union]
  • Norme C++17 (ISO/IEC 14882:2017) :
  • 12.3 Unions [class.union]
  • Norme C++14 (ISO/CEI 14882:2014) :
  • 9.5 Unions [class.union]
  • Norme C++11 (ISO/CEI 14882:2011) :
  • 9.5 Unions [class.union]
  • Norme C++03 (ISO/CEI 14882:2003) :
  • 9.5 Unions [class.union]
  • Norme C++98 (ISO/CEI 14882:1998) :
  • 9.5 Unions [class.union]

Voir aussi

(C++17)
une union discriminée type-safe
(modèle de classe)
Documentation C pour Déclaration d'union