Zoeken…


Invoering

X-macro's zijn een preprocessor-gebaseerde techniek voor het minimaliseren van repetitieve code en het onderhouden van gegevens / code-correspondenties. Meerdere afzonderlijke macro-uitbreidingen op basis van een gemeenschappelijke set gegevens worden ondersteund door de hele groep uitbreidingen weer te geven via een enkele hoofdmacro, waarbij de vervangende tekst van die macro bestaat uit een reeks uitbreidingen van een binnenmacro, één voor elk gegeven. De binnenmacro wordt traditioneel X() , vandaar de naam van de techniek.

Opmerkingen

Van de gebruiker van een master-macro in X-macro-stijl wordt verwacht dat hij zijn eigen definitie voor de innerlijke X() -macro levert, en binnen zijn bereik de master-macro uitbreidt. De innerlijke macroverwijzingen van de master worden dus uitgebreid volgens de gebruikersdefinitie van X() . Op deze manier kan de hoeveelheid repetitieve boilerplate-code in het bronbestand worden verminderd (verschijnt slechts één keer in de vervangende tekst van X() ), zoals wordt aanbevolen door aanhangers van de filosofie "Niet herhalen" (DRY).

Door X() opnieuw te definiëren en de hoofdmacro een of meer extra keren uit te breiden, kunnen X-macro's het onderhouden van overeenkomstige gegevens en code vergemakkelijken - een uitbreiding van de macro declareert de gegevens (bijvoorbeeld als matrixelementen of enumleden), en de andere uitbreidingen produceren overeenkomstige code.

Hoewel de naam "X-macro" afkomstig is van de traditionele naam van de binnenmacro, is de techniek niet afhankelijk van die specifieke naam. Elke geldige macronaam kan in plaats daarvan worden gebruikt.

Kritieken omvatten

  • bronbestanden die afhankelijk zijn van X-macro's zijn moeilijker te lezen;
  • net als alle macro's zijn X-macro's strikt tekstueel - ze bieden inherent geen enkele type veiligheid; en
  • X-macro's zorgen voor het genereren van code . In vergelijking met alternatieven op basis van aanroepfuncties, maken X-macro's de code effectief groter.

Een goede uitleg van X-macro's is te vinden in het artikel [X-Macros] van Randy Meyers in Dr. Dobbs ( http://www.drdobbs.com/the-new-cx-macros/184401387) .

Triviaal gebruik van X-macro's voor 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 */

In dit voorbeeld genereert de preprocessor de volgende code:

printf("X(%d) made this print\n", 1);
printf("X(%d) made this print\n", 2);
printf("X(%d) made this print\n", 3);

Enumwaarde en identificatie

/* 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;
}

Vervolgens kunt u de opgesomde waarde in uw code gebruiken en de identificatie ervan eenvoudig afdrukken met:

printf("%s\n", enum2string(MyEnum_item2));

Uitbreiding: Geef de X-macro als argument

De X-macro-benadering kan een beetje worden gegeneraliseerd door de naam van de "X" -macro een argument van de hoofdmacro te maken. Dit heeft het voordeel dat macro-botsingen worden voorkomen en dat een algemene macro als de "X" -macro kan worden gebruikt.

Zoals altijd met X-macro's, vertegenwoordigt de hoofdmacro een lijst met items waarvan de betekenis specifiek is voor die macro. In deze variatie kan een dergelijke macro als volgt worden gedefinieerd:

/* declare list of items */
#define ITEM_LIST(X) \
      X(item1) \
      X(item2) \
      X(item3) \
/* end of list */

Men zou dan code kunnen genereren om de artikelnamen als volgt af te drukken:

/* define macro to apply */
#define PRINTSTRING(value) printf( #value "\n");

/* apply macro to the list of items */
ITEM_LIST(PRINTSTRING)

Dat breidt zich uit naar deze code:

printf( "item1" "\n"); printf( "item2" "\n"); printf( "item3" "\n");

In tegenstelling tot standaard X-macro's, waar de "X" -naam een ingebouwde eigenschap van de hoofdmacro is, kan het bij deze stijl onnodig of zelfs ongewenst zijn om de macro die als argument wordt gebruikt naderhand PRINTSTRING ( PRINTSTRING in dit voorbeeld).

Code generatie

X-Macro's kunnen worden gebruikt voor het genereren van code, door herhaalde code te schrijven: itereer over een lijst om enkele taken uit te voeren, of om een set constanten, objecten of functies te declareren.

Hier gebruiken we X-macro's om een opsomming met 4 opdrachten en een kaart met hun namen als tekenreeksen te declareren

Vervolgens kunnen we de tekenreekswaarden van het opsomming afdrukken.

/* 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]());

Op dezelfde manier kunnen we een springtabel genereren om functies aan te roepen op basis van de opsommingswaarde.

Dit vereist dat alle functies dezelfde handtekening hebben. Als ze geen argumenten aannemen en een int teruggeven, zouden we dit in een kop zetten met de enum-definitie:

/* 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[];

Al het volgende kan in verschillende compilatie-eenheden zijn, ervan uitgaande dat het bovenstaande gedeelte als koptekst is opgenomen:

/* 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 */}

Een voorbeeld van deze techniek die in echte code wordt gebruikt, is voor GPU-commandoverzending in Chromium .



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow