C Language
X-macros
Recherche…
Introduction
Les macros X sont une technique basée sur un préprocesseur pour minimiser le code répétitif et maintenir les correspondances données / code. Plusieurs expansions de macros distinctes basées sur un ensemble commun de données sont prises en charge en représentant l'ensemble des extensions via une macro maître unique, avec le texte de remplacement de cette macro consistant en une séquence d'expansion d'une macro interne, une pour chaque donnée. La macro interne est traditionnellement nommée X()
, d'où le nom de la technique.
Remarques
L'utilisateur d'une macro principale de style X-macro doit fournir sa propre définition pour la macro X()
interne et, dans son périmètre, développer la macro principale. Les références de macro internes du maître sont donc étendues en fonction de la définition de l'utilisateur de X()
. De cette façon, la quantité de code répétitif du code source dans le fichier source peut être réduite (apparaissant une seule fois dans le texte de remplacement de X()
), comme le préfèrent les adeptes de la philosophie «Ne pas répéter».
De plus, en redéfinissant X()
et en développant la macro principale une ou plusieurs fois de plus, les macros X peuvent faciliter la gestion des données et du code correspondants - une extension de la macro déclare les données (par exemple, éléments de tableau ou enum). les autres extensions produisent le code correspondant.
Bien que le nom "X-macro" provienne du nom traditionnel de la macro interne, la technique ne dépend pas de ce nom particulier. Tout nom de macro valide peut être utilisé à sa place.
Les critiques incluent
- les fichiers sources qui reposent sur les macros X sont plus difficiles à lire;
- comme toutes les macros, les macros X sont strictement textuelles - elles n'offrent pas de sécurité intrinsèque; et
- Les macros X permettent la génération de code . En comparaison avec les alternatives basées sur les fonctions d'appel, les macros X agrandissent efficacement le code.
Une bonne explication des macros X peut être trouvée dans l'article de Randy Meyers [X-Macros] dans Dr. Dobbs ( http://www.drdobbs.com/the-new-cx-macros/184401387) .
Utilisation triviale des macros X pour printfs
/* define a list of preprocessor tokens on which to call X */
#define X_123 X(1) X(2) X(3)
/* define X to use */
#define X(val) printf("X(%d) made this print\n", val);
X_123
#undef X
/* good practice to undef X to facilitate reuse later on */
Cet exemple entraînera le préprocesseur générant le code suivant:
printf("X(%d) made this print\n", 1);
printf("X(%d) made this print\n", 2);
printf("X(%d) made this print\n", 3);
Valeur Enum et Identifiant
/* declare items of the enum */
#define FOREACH \
X(item1) \
X(item2) \
X(item3) \
/* end of list */
/* define the enum values */
#define X(id) MyEnum_ ## id,
enum MyEnum { FOREACH };
#undef X
/* convert an enum value to its identifier */
const char * enum2string(int enumValue)
{
const char* stringValue = NULL;
#define X(id) if (enumValue == MyEnum_ ## id) stringValue = #id;
FOREACH
#undef X
return stringValue;
}
Ensuite, vous pouvez utiliser la valeur énumérée dans votre code et imprimer facilement son identifiant en utilisant:
printf("%s\n", enum2string(MyEnum_item2));
Extension: Donne la macro X comme argument
L'approche X-macro peut être généralisée en faisant du nom de la macro "X" un argument de la macro maître. Cela présente l'avantage d'éviter les collisions de noms de macro et de permettre l'utilisation d'une macro à usage général comme macro "X".
Comme toujours avec les macros X, la macro principale représente une liste d'éléments dont la signification est spécifique à cette macro. Dans cette variante, une telle macro peut être définie comme suit:
/* declare list of items */
#define ITEM_LIST(X) \
X(item1) \
X(item2) \
X(item3) \
/* end of list */
On pourrait alors générer du code pour imprimer les noms d'élément comme suit:
/* define macro to apply */
#define PRINTSTRING(value) printf( #value "\n");
/* apply macro to the list of items */
ITEM_LIST(PRINTSTRING)
Cela étend à ce code:
printf( "item1" "\n"); printf( "item2" "\n"); printf( "item3" "\n");
Contrairement aux macros X standard, où le nom "X" est une caractéristique intégrée de la macro maître, avec ce style, il peut s'avérer inutile, voire indésirable, de PRINTSTRING
ultérieurement la macro utilisée comme argument ( PRINTSTRING
dans cet exemple).
Génération de code
Les macros X peuvent être utilisées pour la génération de code, en écrivant du code répétitif: parcourez une liste pour effectuer certaines tâches ou pour déclarer un ensemble de constantes, d'objets ou de fonctions.
Ici, nous utilisons des macros X pour déclarer un enum contenant 4 commandes et une carte de leurs noms sous forme de chaînes
Ensuite, nous pouvons imprimer les valeurs de chaîne de l’enum.
/* All our commands */
#define COMMANDS(OP) OP(Open) OP(Close) OP(Save) OP(Quit)
/* generate the enum Commands: {cmdOpen, cmdClose, cmdSave, cmdQuit, }; */
#define ENUM_NAME(name) cmd##name,
enum Commands {
COMMANDS(ENUM_NAME)
};
#undef ENUM_NAME
/* generate the string table */
#define COMMAND_OP(name) #name,
const char* const commandNames[] = {
COMMANDS(COMMAND_OP)
};
#undef COMMAND_OP
/* the following prints "Quit\n": */
printf("%s\n", commandNames[cmdQuit]());
De même, nous pouvons générer une table de saut pour appeler les fonctions par la valeur enum.
Cela nécessite que toutes les fonctions aient la même signature. S'ils ne prennent aucun argument et renvoient un int, nous placerions ceci dans un en-tête avec la définition enum:
/* declare all functions as extern */
#define EXTERN_FUNC(name) extern int doCmd##name(void);
COMMANDS(EXTERN_FUNC)
#undef EXTERN_FUNC
/* declare the function pointer type and the jump table */
typedef int (*CommandFunc)(void);
extern CommandFunc commandJumpTable[];
Tous les éléments suivants peuvent être dans des unités de compilation différentes en supposant que la partie ci-dessus est incluse en tant qu'en-tête:
/* generate the jump table */
#define FUNC_NAME(name) doCmd##name,
CommandFunc commandJumpTable[] = {
COMMANDS(FUNC_NAME)
};
#undef FUNC_NAME
/* call the save command like this: */
int result = commandJumpTable[cmdSave]();
/* somewhere else, we need the implementations of the commands */
int doCmdOpen(void) {/* code performing open command */}
int doCmdClose(void) {/* code performing close command */}
int doCmdSave(void) {/* code performing save command */}
int doCmdQuit(void) {/* code performing quit command */}
Un exemple de cette technique utilisée dans le code réel est la distribution de commandes GPU dans Chrome .