Je trouve les syndicats C ++ plutôt cool. Il semble que les gens ne pensent généralement qu'au cas d'utilisation où l'on veut changer la valeur d'une instance d'union "en place" (qui, semble-t-il, ne sert qu'à économiser de la mémoire ou à effectuer des conversions douteuses).
En fait, les syndicats peuvent être d'une grande puissance en tant qu'outil d'ingénierie logicielle, même si vous ne modifiez jamais la valeur d'une instance d'union .
Cas d'utilisation 1: le caméléon
Avec les unions, vous pouvez regrouper un certain nombre de classes arbitraires sous une dénomination, ce qui n'est pas sans similitudes avec le cas d'une classe de base et de ses classes dérivées. Ce qui change, cependant, c'est ce que vous pouvez et ne pouvez pas faire avec une instance d'union donnée:
struct Batman;
struct BaseballBat;
union Bat
{
Batman brucewayne;
BaseballBat club;
};
ReturnType1 f(void)
{
BaseballBat bb = {/* */};
Bat b;
b.club = bb;
// do something with b.club
}
ReturnType2 g(Bat& b)
{
// do something with b, but how do we know what's inside?
}
Bat returnsBat(void);
ReturnType3 h(void)
{
Bat b = returnsBat();
// do something with b, but how do we know what's inside?
}
Il apparaît que le programmeur doit être certain du type de contenu d'une instance d'union donnée lorsqu'il veut l'utiliser. C'est le cas en fonction f
ci-dessus. Cependant, si une fonction recevait une instance d'union en tant qu'argument passé, comme c'est le cas avec g
ci-dessus, alors elle ne saurait pas quoi en faire. La même chose s'applique aux fonctions retournant une instance d'union, voir h
: comment l'appelant sait-il ce qu'il y a à l'intérieur?
Si une instance d'union n'est jamais passée comme argument ou comme valeur de retour, alors elle aura forcément une vie très monotone, avec des pics d'excitation lorsque le programmeur choisit de changer son contenu:
Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;
Et c'est le cas d'utilisation le plus (non) populaire des syndicats. Un autre cas d'utilisation est lorsqu'une instance d'union vient avec quelque chose qui vous indique son type.
Cas d'utilisation 2: "Ravi de vous rencontrer, je suis object
de Class
"
Supposons qu'un programmeur choisisse de toujours jumeler une instance d'union avec un descripteur de type (je laisserai à la discrétion du lecteur le soin d'imaginer une implémentation pour un tel objet). Cela va à l'encontre du but de l'union elle-même si ce que veut le programmeur est d'économiser de la mémoire et que la taille du descripteur de type n'est pas négligeable par rapport à celle de l'union. Mais supposons qu'il soit crucial que l'instance d'union puisse être passée comme argument ou comme valeur de retour avec l'appelé ou l'appelant ne sachant pas ce qu'il y a à l'intérieur.
Ensuite, le programmeur doit écrire une switch
instruction de flux de contrôle pour distinguer Bruce Wayne d'un bâton en bois, ou quelque chose d'équivalent. Ce n'est pas trop mal quand il n'y a que deux types de contenus dans le syndicat mais évidemment, le syndicat ne s'adapte plus.
Cas d'utilisation 3:
Comme les auteurs d' une recommandation pour la norme ISO C ++ l' ont remis en 2008,
De nombreux domaines problématiques importants nécessitent un grand nombre d'objets ou des ressources mémoire limitées. Dans ces situations, il est très important de conserver de l'espace et une union est souvent un moyen idéal pour y parvenir. En fait, un cas d'utilisation courant est la situation où un syndicat ne change jamais de membre actif au cours de sa vie. Il peut être construit, copié et détruit comme s'il s'agissait d'une structure contenant un seul membre. Une application typique de ceci serait de créer une collection hétérogène de types non liés qui ne sont pas alloués dynamiquement (peut-être sont-ils construits en place dans une carte, ou membres d'un tableau).
Et maintenant, un exemple, avec un diagramme de classes UML:
La situation en anglais simple: un objet de classe A peut avoir des objets de n'importe quelle classe parmi B1, ..., Bn, et au plus un de chaque type, n étant un assez grand nombre, disons au moins 10.
Nous ne voulons pas ajouter des champs (membres de données) à A comme ceci:
private:
B1 b1;
.
.
.
Bn bn;
parce que n peut varier (nous pourrions vouloir ajouter des classes Bx au mélange), et parce que cela causerait un désordre avec les constructeurs et parce que les objets A prendraient beaucoup de place.
Nous pourrions utiliser un conteneur farfelu de void*
pointeurs vers des Bx
objets avec des castes pour les récupérer, mais c'est fugace et donc de style C ... mais plus important encore, cela nous laisserait la durée de vie de nombreux objets alloués dynamiquement à gérer.
Au lieu de cela, ce qui peut être fait est ceci:
union Bee
{
B1 b1;
.
.
.
Bn bn;
};
enum BeesTypes { TYPE_B1, ..., TYPE_BN };
class A
{
private:
std::unordered_map<int, Bee> data; // C++11, otherwise use std::map
public:
Bee get(int); // the implementation is obvious: get from the unordered map
};
Ensuite, pour obtenir le contenu d'une instance d'union data
, vous utilisez a.get(TYPE_B2).b2
et les likes, où a
est une A
instance de classe .
C'est d'autant plus puissant que les unions sont illimitées dans C ++ 11. Voir le document lié ci - dessus ou cet article pour plus de détails.