Je n'ai pas encore vraiment syndicats. Je veux dire que je ne les comprends. Chaque C ou C++ texte je passe par l'introduit (parfois en passant), mais ils ont tendance à donner très peu d'exemples concrets de leur utilisation ou de la région applicable ou de l'utilisation. Quand les syndicats d'être utile dans un cadre moderne ou même héritage cas? Mes deux seuls imagine la programmation des microprocesseurs lorsque vous avez très peu d'espace pour travailler avec, ou lorsque vous êtes à l'élaboration d'un API ou de quelque chose et que vous voulez forcer l'utilisateur final n'avoir qu'une seule instance de plusieurs objets de types à la fois. Ce sont ces deux suppositions, même à proximité de droit? Quelqu'un pourrait-il élaborer pour moi? Merci! -R
Réponses
Trop de publicités?Les syndicats sont généralement utilisés avec la compagnie d'un discriminateur: une variable indiquant lequel des champs de l'union est valide. Par exemple, disons que vous souhaitez créer votre propre Variante de type:
struct my_variant_t {
int type;
union {
char char_value;
short short_value;
int int_value;
long long_value;
float float_value;
double double_value;
void* ptr_value;
};
};
Alors que vous pouvez l'utiliser comme:
/* construct a new float variant instance */
void init_float(struct my_variant_t* v, float initial_value) {
v->type = VAR_FLOAT;
v->float_value = initial_value;
}
/* Increments the value of the variant by the given int */
void inc_variant_by_int(struct my_variant_t* v, int n) {
switch (v->type) {
case VAR_FLOAT:
v->float_value += n;
break;
case VAR_INT:
v->int_value += n;
break;
...
}
}
Ce est en fait assez commun idiome, spécialement sur Visual Basic internes.
Pour un exemple réel de voir SDL SDL_Event de l'union. (code source ici). Il y a un type
champ au sommet de l'union, et le même champ est répété sur tous les SDL_*struct. Ensuite, pour gérer l'événement correct, vous devez vérifier la valeur de l' type
champ.
Les avantages sont simples: il y a un type de données unique pour gérer tous les types d'événements sans l'aide de la mémoire nécessaire.
Je trouve C++ syndicats assez cool. Il semble que les gens en général ne pense que du cas d'utilisation où l'on veut modifier la valeur d'une union instance "à la place" (qui, paraît-il, ne sert qu'à économiser de la mémoire ou d'effectuer douteux conversions).
En fait, les syndicats peuvent être de grande puissance comme un logiciel outil d'ingénierie, même lorsque vous ne changez jamais la valeur d'une union de l'instance.
Cas d'utilisation 1: le caméléon
Avec les syndicats, vous pouvez les regrouper un certain nombre de l'arbitraire des classes sous une dénomination, qui n'est pas sans similitudes avec le cas d'une classe de base et de ses classes dérivées. Quels changements, cependant, est ce que vous pouvez et ne pouvez pas faire avec un syndicat exemple:
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 semble que le programmeur doit en être certain du type de contenu d'un syndicat exemple quand il veut l'utiliser. C'est le cas dans la fonction f
- dessus. Toutefois, si une fonction a été de recevoir une union instance comme un argument passé, comme c'est le cas avec g
- dessus, alors il ne sait pas quoi faire avec elle. La même chose s'applique à des fonctions retournant une instance de l'union, voir h
: comment l'appelant savoir ce qui est à l'intérieur?
Si une instance de l'union n'est jamais passé comme argument ou comme valeur de retour, alors il est lié à une très monotone de la vie, avec des pointes 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 plus (de l'onu)des cas d'utilisation de syndicats. Un autre cas est quand une union instance vient avec quelque chose qui vous indique son type.
Cas d'utilisation 2: "ravi de vous rencontrer, je suis en object
, à partir de Class
"
Supposons qu'un programmeur élu pour toujours paire en place d'une union avec un descripteur de type (je vais le laisser au lecteur la liberté d'imaginer une mise en œuvre de l'un de ces objets). Cela va à l'encontre de l'objectif de l'union elle-même, si ce que le programmeur veut, c'est pour é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 est crucial que l'union instance peut être passé comme argument ou comme valeur de retour avec le destinataire de l'appel ou de l'appelant ne sachant pas ce qui est à l'intérieur.
Ensuite, le programmeur doit écrire un switch
contrôle de l'état des flux de dire à Bruce Wayne en dehors d'un bâton de bois, ou quelque chose d'équivalent. Il n'est pas trop mauvais quand il y a seulement deux types de contenu dans l'union, mais de toute évidence, l'union n'a pas d'échelle de plus.
Cas d'utilisation 3:
Comme les auteurs d' une recommandation pour le Standard ISO C++ le mettre de nouveau en 2008,
Beaucoup de problème important domaines nécessitent une grande quantité d'objets ou de la mémoire limitée les ressources. Dans ces situations, la conservation de l'espace est très important, et une union est souvent un moyen idéal pour le faire. En fait un cas d'utilisation est la situation où un syndicat ne change jamais sa membre actif au cours de sa durée de vie. Il peut être construit, copié, et détruits comme si c'était une structure contenant un seul membre. Une application typique de ce serait de créer une collection hétérogène de sans rapport avec les types qui ne sont pas attribuées de manière dynamique (peut-être qu'ils sont en place, construit sur une carte, ou des membres d'un tableau).
Et maintenant, un exemple, avec un diagramme de classe UML:
La situation dans la plaine de l'anglais: un objet de classe A peuvent avoir des objets de n'importe quelle classe parmi B1, ..., Bn, et au plus un de chaque type, avec n étant un assez grand nombre, dire au moins 10.
Nous ne voulons pas ajouter des champs (données membres) à la Une de la sorte:
private:
B1 b1;
.
.
.
Bn bn;
parce que n peut varier (on peut vouloir ajouter Bx classes pour le mix), et parce que ce serait la cause d'un désordre avec des constructeurs et parce que les objets prennent beaucoup d'espace.
On pourrait utiliser un peu farfelu conteneur d' void*
des pointeurs vers Bx
objets avec des moulages pour les récupérer, mais c'est moche et tellement de style C... mais le plus important est que nous laisserait avec les durées de vie de beaucoup d'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'un syndicat exemple, à partir d' data
, vous devez utiliser a.get(TYPE_B2).b2
et les aime, où l' a
est une classe A
de l'instance.
Cela est d'autant plus puissant puisque les syndicats sont libres de C++11. Voir le document lié ci-dessus ou cet article pour plus de détails.
Herb Sutter http://en.wikipedia.org/wiki/Herb_Sutter écrit dans GOTW environ six ans, avec l'accent a ajouté:
"Mais ne crois pas que les syndicats sont seulement un vestige des temps anciens. Les syndicats sont peut-être plus utile pour économiser de l'espace en permettant de données de chevauchement, et c'est encore souhaitable en C++ et dans le monde moderne d'aujourd'hui. Par exemple, certains des plus avancées du C++ standard library implantations dans le monde utilisent maintenant seulement cette technique pour la mise en œuvre de la "petite optimisation de la chaîne," une grande optimisation de l'alternative qui réutilise le stockage à l'intérieur d'une chaîne de caractères de l'objet lui-même: pour les grandes chaînes, l'espace à l'intérieur de la chaîne de magasins d'objets habituels pointeur vers le tampon allouée dynamiquement et d'entretien ménager des informations comme la taille de la mémoire tampon; pour les petites chaînes, le même espace est plutôt réutilisés pour stocker le contenu de la chaîne directement et éviter toute allocation dynamique de la mémoire. Pour en savoir plus sur la petite optimisation de la chaîne (et d'autres de la chaîne d'optimisations et pessimizations en profondeur), à voir... ."
Et pour moins d'un utile exemple, voir la longue, mais peu concluante à la question de gcc, strict-aliasing, et le moulage par une union.
Eh bien, un exemple de cas d'utilisation, je pense à ceci:
typedef union
{
struct
{
uint8_t a;
uint8_t b;
uint8_t c;
uint8_t d;
};
uint32_t x;
} some32bittype;
Vous pouvez ensuite accéder à la 8-bits de séparer les parties de 32 bits bloc de données; cependant, préparez-vous à potentiellement être mordu par l'endianness.
Ce n'est qu'un exemple hypothétique, mais à chaque fois que vous voulez diviser les données dans un champ en composant des pièces comme cela, vous pouvez utiliser un syndicat.
Cela dit, il y a aussi une méthode qui est endian-coffre-fort:
uint32_t x;
uint8_t a = (x & 0xFF000000) >> 24;
Par exemple, depuis que l'opération binaire sera converti par le compilateur à la bonne boutisme.