Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
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
tag
opt{
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 :
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 membresprivate
etprotected
dans une union anonyme génère des erreurs. - Elle ne peut pas avoir de fonctions membres.