J'ai appris mais je n'ai pas vraiment de syndicats. Chaque texte C ou C ++ que je traverse les présente (parfois en passant), mais ils ont tendance à donner très peu d'exemples pratiques expliquant pourquoi et où les utiliser. Quand les syndicats seraient-ils utiles dans un cas moderne (ou même hérité)? Mes deux seules hypothèses seraient de programmer des microprocesseurs lorsque vous disposez d'un espace très limité pour travailler, ou lorsque vous développez une API (ou quelque chose de similaire) et que vous voulez forcer l'utilisateur final à n'avoir qu'une seule instance de plusieurs objets / types à une fois. Ces deux suppositions sont-elles encore proches de la bonne?
133
Réponses:
Les syndicats sont généralement utilisés avec la société d'un discriminateur: une variable indiquant lequel des champs de l'union est valide. Par exemple, disons que vous souhaitez créer votre propre type de Variant :
Ensuite, vous l'utiliseriez comme:
C'est en fait un idiome assez courant, en particulier sur les composants internes de Visual Basic.
Pour un exemple concret, consultez l' union SDL_Event de SDL . ( code source réel ici ). Il y a un
type
champ en haut de l'union, et le même champ est répété sur chaque structure d'événement SDL_ *. Ensuite, pour gérer l'événement correct, vous devez vérifier la valeur dutype
champ.Les avantages sont simples: il existe un seul type de données pour gérer tous les types d'événements sans utiliser de mémoire inutile.
la source
struct object
dans github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.cJe 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:
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 avecg
ci-dessus, alors elle ne saurait pas quoi en faire. La même chose s'applique aux fonctions retournant une instance d'union, voirh
: 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:
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
deClass
"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,
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:
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 desBx
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:
Ensuite, pour obtenir le contenu d'une instance d'union
data
, vous utiliseza.get(TYPE_B2).b2
et les likes, oùa
est uneA
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.
la source
Un exemple est dans le domaine intégré, où chaque bit d'un registre peut signifier quelque chose de différent. Par exemple, une union d'un entier de 8 bits et d'une structure avec 8 champs de bits séparés de 1 bit vous permet de changer un bit ou l'octet entier.
la source
void*
s explicites ou des masques et des décalages.REG |= MASK
etREG &= ~MASK
. Si cela est sujet aux erreurs, mettez-les dans un#define SETBITS(reg, mask)
et#define CLRBITS(reg, mask)
. Ne comptez pas sur le compilateur pour obtenir les bits dans un certain ordre ( stackoverflow.com/questions/1490092/… )Herb Sutter a écrit dans GOTW il y a environ six ans, en insistant sur :
Et pour un exemple moins utile, voyez la question longue mais peu concluante gcc, strict-aliasing et casting through a union .
la source
Eh bien, un exemple de cas d'utilisation auquel je peux penser est celui-ci:
Vous pouvez ensuite accéder aux parties séparées 8 bits de ce bloc de données 32 bits; cependant, préparez-vous à être potentiellement mordu par l'endian.
Ceci n'est qu'un exemple hypothétique, mais chaque fois que vous souhaitez fractionner des données dans un champ en composants comme celui-ci, vous pouvez utiliser une union.
Cela dit, il existe également une méthode qui est sûre pour les endians:
Par exemple, puisque cette opération binaire sera convertie par le compilateur à l'endianness correcte.
la source
Quelques utilisations pour les syndicats:
Économie d'espace de stockage lorsque les champs dépendent de certaines valeurs:
Grep les fichiers include à utiliser avec votre compilateur. Vous trouverez des dizaines à des centaines d'utilisations de
union
:la source
Les unions sont utiles pour traiter des données de niveau octet (bas niveau).
Une de mes utilisations récentes concernait la modélisation d'adresse IP qui ressemble à ci-dessous:
la source
Un exemple quand j'ai utilisé un syndicat:
cela me permet d'accéder à mes données sous forme de tableau ou d'éléments.
J'ai utilisé un syndicat pour que les différents termes indiquent la même valeur. En traitement d'image, que je travaille sur les colonnes ou la largeur ou la taille dans le sens X, cela peut devenir déroutant. Pour résoudre ce problème, j'utilise un syndicat afin de savoir quelles descriptions vont ensemble.
la source
Les syndicats fournissent le polymorphisme chez C.
la source
void*
fait ça ^^Une utilisation brillante de l'union est l'alignement de la mémoire, que j'ai trouvé dans le code source PCL (Point Cloud Library). La structure de données unique dans l'API peut cibler deux architectures: CPU avec prise en charge SSE ainsi que CPU sans prise en charge SSE. Par exemple: la structure de données de PointXYZ est
Les 3 flotteurs sont rembourrés avec un flotteur supplémentaire pour l'alignement SSE. Donc pour
L'utilisateur peut accéder à point.data [0] ou point.x (selon le support SSE) pour accéder, par exemple, à la coordonnée x. Des détails d'utilisation plus similaires sont disponibles sur le lien suivant: Documentation PCL Types PointT
la source
Le
union
mot-clé, bien qu'il soit encore utilisé en C ++ 03 1 , est principalement un vestige des jours C. Le problème le plus flagrant est qu'il ne fonctionne qu'avec le POD 1 .L'idée de l'union, cependant, est toujours présente, et en effet les bibliothèques Boost disposent d'une classe de type union:
Ce qui présente la plupart des avantages du
union
(sinon tous) et ajoute:Dans la pratique, il a été démontré qu'il était équivalent à une combinaison de
union
+enum
, et évalué qu'il était aussi rapide (alors queboost::any
c'est plus du domaine dedynamic_cast
, puisqu'il utilise RTTI).1 Les unions ont été mises à niveau en C ++ 11 ( unions illimitées ) et peuvent désormais contenir des objets avec des destructeurs, bien que l'utilisateur doive invoquer le destructeur manuellement (sur le membre de l'union actuellement actif). Il est toujours beaucoup plus facile d'utiliser des variantes.
la source
boost::variant
que d'essayer d'utiliser les syndicats seuls. Il y a beaucoup trop de comportements indéfinis dans les syndicats pour que vos chances de réussir sont abyssales.Extrait de l'article de Wikipédia sur les syndicats :
la source
Dans les premiers jours de C (par exemple comme documenté en 1974), toutes les structures partageaient un espace de noms commun pour leurs membres. Chaque nom de membre était associé à un type et à un décalage; si "wd_woozle" était un "int" à l'offset 12, alors un pointeur
p
de n'importe quel type de structurep->wd_woozle
serait équivalent à*(int*)(((char*)p)+12)
. Le langage exigeait que tous les membres de tous les types de structures aient des noms uniques, sauf qu'il autorisait explicitement la réutilisation des noms de membres dans les cas où chaque structure où ils étaient utilisés les traitait comme une séquence initiale commune.Le fait que les types de structure puissent être utilisés de manière promiscue a permis aux structures de se comporter comme si elles contenaient des champs se chevauchant. Par exemple, des définitions données:
code pourrait déclarer une structure de type "float1" et ensuite utiliser "membres" b0 ... b3 pour y accéder aux octets individuels. Lorsque la langue était modifiée pour que chaque structure reçoive un espace de noms distinct pour ses membres, le code qui reposait sur la possibilité d'accéder aux choses de plusieurs manières se cassait. Les valeurs de séparation des espaces de noms pour différents types de structure étaient suffisantes pour exiger que ce code soit modifié pour l'adapter, mais la valeur de ces techniques était suffisante pour justifier l'extension du langage pour continuer à le supporter.
Code qui avait été écrit pour exploiter la capacité d'accéder au stockage dans un
struct float1
comme si elle était unstruct byte4
pourrait être fait pour travailler dans la nouvelle langue en ajoutant une déclaration:union f1b4 { struct float1 ff; struct byte4 bb; };
, déclarant des objets en tant que typeunion f1b4;
plutôt questruct float1
, et le remplacement des accès àf0
,b0
,b1
, etc. . avecff.f0
,bb.b0
,bb.b1
, etc. Bien qu'il existe de meilleures façons ce code aurait pu être pris en charge, l'union
approche était au moins un peu réalisable, au moins des interprétations C89 datant de l' époque des règles d'aliasing.la source
Disons que vous avez n types de configurations différents (étant simplement un ensemble de variables définissant des paramètres). En utilisant une énumération des types de configuration, vous pouvez définir une structure qui a l'ID du type de configuration, ainsi qu'une union de tous les différents types de configurations.
De cette façon, partout où vous passez la configuration peut utiliser l'ID pour déterminer comment interpréter les données de configuration, mais si les configurations étaient énormes, vous ne seriez pas obligé d'avoir des structures parallèles pour chaque type potentiel gaspillant de l'espace.
la source
Un coup de pouce récent sur l'importance déjà élevée des unions a été donné par la règle stricte d'aliasing introduite dans la version récente du standard C.
Vous pouvez utiliser les unions do pour taper des punaises sans violer la norme C.
Ce programme a un comportement non spécifié (car je l'ai supposé
float
etunsigned int
a la même longueur) mais pas un comportement indéfini (voir ici ).la source
Je voudrais ajouter un bon exemple pratique d'utilisation de l'union - implémentation d'un calculateur / interprète de formule ou utilisation d'une sorte de calcul dans le calcul (par exemple, vous voulez utiliser des parties modifiables pendant l'exécution de vos formules de calcul - résoudre une équation numériquement - juste par exemple). Vous voudrez peut-être définir des nombres / constantes de différents types (nombres entiers, flottants, même complexes) comme ceci:
Donc, vous économisez de la mémoire et ce qui est plus important - vous évitez toute allocation dynamique pour une quantité probablement extrême (si vous utilisez beaucoup de nombres définis au moment de l'exécution) de petits objets (par rapport aux implémentations via l'héritage de classe / polymorphisme). Mais ce qui est plus intéressant, vous pouvez toujours utiliser la puissance du polymorphisme C ++ (si vous êtes fan du double dispatching, par exemple;) avec ce type de struct. Ajoutez simplement un pointeur d'interface "factice" à la classe parente de tous les types de nombres en tant que champ de cette structure, pointant vers cette instance au lieu de / en plus du type brut, ou utilisez de bons vieux pointeurs de fonction C.
vous pouvez donc utiliser le polymorphisme au lieu des vérifications de type avec switch (type) - avec une implémentation efficace en mémoire (pas d'allocation dynamique de petits objets) - si vous en avez besoin, bien sûr.
la source
Depuis http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :
la source
Les syndicats offrent un moyen de manipuler différents types de données dans une seule zone de stockage sans intégrer aucune information indépendante de la machine dans le programme.Ils sont analogues aux enregistrements de variantes dans pascal
À titre d'exemple comme celui que l'on peut trouver dans un gestionnaire de table de symboles de compilateur, supposons qu'une constante puisse être un int, un flottant ou un pointeur de caractère. La valeur d'une constante particulière doit être stockée dans une variable du type approprié, mais il est plus pratique pour la gestion de table si la valeur occupe la même quantité de stockage et est stockée au même endroit quel que soit son type. C'est le but d'une union - une variable unique qui peut légitimement contenir l'un des différents types. La syntaxe est basée sur des structures:
La variable u sera suffisamment grande pour contenir le plus grand des trois types; la taille spécifique dépend de l'implémentation. N'importe lequel de ces types peut être attribué à u puis utilisé dans des expressions, tant que l'utilisation est cohérente
la source