Unions

Les structures ont pour taille approximativement la somme des tailles de leurs composants. Leur taille peut donc devenir très grande. Or il arrive que certains champs ne soient pas utilisés lorsque d’autres le sont, parce qu’ils sont mutuellement incompatibles.

Pour résoudre partiellement ce problème, le langage fournit les unions. Il s’agit de groupes de données, comme les structures, mais au lieu de se trouver placées les unes derrière les autres en mémoire, elles se trouvent toutes à la même adresse. Par exemple, l’union suivante :

union longgroupe {     long l;     unsigned mots[2];     unsigned char octets[4];     }

n’occupe que quatre octets en mémoire (on suppose ici que c’est la taille des entiers long, mais cela peut être différent sur votre ordinateur). De la sorte, si l’on écrit :

longgroupe lg;lg.l = 100000;

comme 100000 = 0x186A0, on trouvera dans lg.mots les valeurs { 0x86A0, 0x1 } (soit 34464 et 1) (sur PC, le mots de poids faibles sont placés en premier) et dans lg.octets { 0xA0, 0x86, 0x1, 0x0 } (soit 160, 134, 1 et 0). On a ainsi un moyen simple de décomposer un entier long, ou n’importe quoi d’autre, en octets.

On peut initialiser une union en donnant entre accolades la valeur de son premier champ, comme ceci :

longgroupe lg = { 100000 };

D’une façon générale, une union peut contenir autant de champs de n’importe quel genre que souhaité, mais ils se trouvent tous à la même adresse, de sorte que la taille de la structure est celle du plus long de ces champs. En outre, l’union ne sait pas quel champ « est le bon » , en ce sens que n’importe lequel peut être modifié à tout moment, avec des répercussions sur tous les autres. C’est pourquoi il est peu recommandé de placer des informations différentes dans une union simplement pour récupérer de la place, si l’on ne dispose pas d’un moyen simple pour savoir quelle est le champ qui correspond à une information valable.

Un tel moyen consiste à utiliser les unions comme champs de structures ou mieux encore de classes. Prenons un exemple (un peu bête car il est difficile de ne pas en prendre un artificiel, vu qu’il existe souvent de meilleurs systèmes) : nous disposons d’un fichier avec les noms complets de personnes. En Occident, le nom complet est généralement formé de deux mots, ici arbitrairement limités à 15 caractères. Dans certains pays, en Orient notamment, il est composé de trois mots plus petits (10 caractères), dont seul le premier représente le nom de famille, les deux autres formant le prénom. Voici un exemple de classe qui peut indifféremment stocker un nom oriental ou occidental. Un champ spécial indique si l’on est en présence de l’une ou l’autre alternative :

class noms {     char oriental;    // 1 = oriental, 0 = occidental     union {         char nomorient[3][10];         char nomoccident[2][15];         };     public :     noms(char *nom, char *prenom1, char *prenom2 = 0)         { oriental = (prenom2 != 0);           if (oriental) {             strncpy(nomorient[0], nom, 10);             strncpy(nomorient[1], prenom1, 10);             strncpy(nomorient[2], prenom2, 10);             }           else {             strncpy(nomoccident[0], nom, 15);             strncpy(nomoccident[1], prenom1, 15);             }         }     char *nom(void)         { if (oriental) return nomorient[0];          else return nomoccident[0];         }     char *prenom1(void)         { if (oriental) return nomorient[1];          else return nomoccident[1]; }     char *prenom2(void)         { if (oriental) return nomorient[2];          else return ""; }     char *prenomcomplet(void)         { static char tampon[22];           if (oriental) {             strcpy(tampon, nomorient[1]);             strcat(tampon, "-");             strcat(tampon, nomorient[2]);             return tampon;             }           else return nomoccident[1];         }     };

Noter qu’il n’est pas nécessaire de préciser un nom de champ pour l’union, les noms des champs internes suffisent. Par contre, quand une union contient un champ de type structure, classe ou union, il faut lui donner un nom.

Le constructeur suppose que lorsque aucun second prénom n’est précisé, il s’agit d’un nom occidental. On peut donc écrire :

noms occ("Dupont", "Jean");noms ori("Fang", "Li", "Zi");

Nous laissons au lecteur le soin d’écrire une fonction renvoyant le nom complet, avec la convention que le prénom vient en premier en occident, tandis qu’il vient en dernier en orient.

Exercice 6.8

Trouvez un moyen plus simple d’implanter une telle classe, sans utiliser d’union.

Voir solution

Cet exemple illustre le fait que les unions sont nettement moins dangereuses lorsqu’elles sont membres de classes contenant un indicateur qui les contrôle.

Les unions peuvent avoir des méthodes et des constructeurs, mais tous leurs membres sont obligatoirement publics ; en outre, Turbo C++ n’accepte pas que les unions anonymes aient des méthodes.

Exercice 6.9

Sans écrire les méthodes, donner une implantation d’une classe pouvant contenir soit un nom et un prénom de 15 caractères chacun, soit un nom de 15 caractères, un prénom de 14 et une initiale intermédiaire de 1 (nom américain), soit un nom en trois parties à l’orientale.

Voir solution

Sauf pour des décompositions comme longgroupe l’intérêt des unions est assez faible en C++, d’autant que, contrairement à ce qui se passe par exemple en Pascal, les parties qui se recouvrent dans une union sont nécessairement réduites à un seul élément, et que l’union elle-même est tout entière en mode de recouvrement, ce qui oblige à utiliser des structures imbriquées compliquées pour faire recouvrir des données différentes.

Précédent Précédent Sommaire Sommaire Suivant Suivant