Szukaj…


Wprowadzenie

Makra X to technika oparta na preprocesorze w celu minimalizacji powtarzalności kodu i utrzymywania korespondencji danych / kodów. Obsługiwanych jest wiele różnych rozszerzeń makr opartych na wspólnym zestawie danych poprzez reprezentowanie całej grupy rozszerzeń za pomocą jednego makra głównego, przy czym tekst zastępujący to makro składa się z sekwencji rozwinięć makra wewnętrznego, po jednym dla każdego układu odniesienia. Wewnętrzne makro tradycyjnie nosi nazwę X() , stąd nazwa techniki.

Uwagi

Użytkownik makra głównego w stylu makra X powinien podać własną definicję wewnętrznego makra X() oraz w swoim zakresie rozszerzyć makro główne. Wewnętrzne referencje makr głównego są zatem rozszerzane zgodnie z definicją X() . W ten sposób można zmniejszyć ilość powtarzającego się kodu w pliku źródłowym (pojawia się tylko raz, w tekście zastępczym X() ), jak to sprzyjają zwolennicy filozofii „Don't Repeat Yourself” (DRY).

Ponadto poprzez ponowne zdefiniowanie X() i rozwinięcie makra głównego jeden lub więcej razy więcej, makra X mogą ułatwić utrzymanie odpowiednich danych i kodu - jedno rozwinięcie makra deklaruje dane (na przykład jako elementy tablicy lub elementy wyliczeniowe), oraz inne rozszerzenia generują odpowiedni kod.

Chociaż nazwa „X-makro” pochodzi od tradycyjnej nazwy makro wewnętrznego, technika nie zależy od tej konkretnej nazwy. Zamiast niego można użyć dowolnej poprawnej nazwy makra.

Krytyki obejmują

  • pliki źródłowe oparte na makrach X są trudniejsze do odczytania;
  • podobnie jak wszystkie makra, makra X są ściśle tekstowe - z natury nie zapewniają żadnego bezpieczeństwa; i
  • Makra X zapewniają generowanie kodu . W porównaniu do alternatyw opartych na funkcjach wywoływania, makra X skutecznie powiększają kod.

Dobre objaśnienie makr X można znaleźć w artykule Randy'ego Meyersa [X-Macros] w Dr. Dobbs ( http://www.drdobbs.com/the-new-cx-macros/184401387) .

Trywialne użycie makr X do 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 */

W tym przykładzie preprocesor wygeneruje następujący kod:

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

Wartość wyliczenia i identyfikator

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

Następnie możesz użyć wartości wyliczonej w kodzie i łatwo wydrukować jej identyfikator, używając:

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

Rozszerzenie: Podaj makro X jako argument

Podejście do makropolecenia X można nieco uogólnić, czyniąc nazwę makra „X” argumentem makra głównego. Ma to tę zaletę, że pomaga uniknąć kolizji nazw makr i umożliwia użycie makra ogólnego przeznaczenia jako makra „X”.

Jak zawsze w przypadku makr X, makro master reprezentuje listę elementów, których znaczenie jest specyficzne dla tego makra. W tej odmianie takie makro można zdefiniować następująco:

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

Następnie można wygenerować kod, aby wydrukować nazwy elementów w następujący sposób:

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

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

To rozwija się do tego kodu:

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

W przeciwieństwie do standardowych makr X, w których nazwa „X” jest wbudowaną cechą makra głównego, w tym stylu może być niepotrzebne lub nawet niepożądane późniejsze zdefiniowanie makra użytego jako argument ( PRINTSTRING w tym przykładzie).

Generowanie kodu

Makra X mogą być używane do generowania kodu, pisząc powtarzalny kod: iteruj po liście, aby wykonać niektóre zadania lub zadeklarować zestaw stałych, obiektów lub funkcji.

Tutaj używamy makr X do deklarowania wyliczenia zawierającego 4 polecenia i mapę ich nazw jako ciągów znaków

Następnie możemy wydrukować wartości ciągu wyliczenia.

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

Podobnie możemy wygenerować tabelę skoków do wywoływania funkcji według wartości wyliczeniowej.

Wymaga to, aby wszystkie funkcje miały ten sam podpis. Jeśli nie przyjmą argumentów i nie zwrócą wartości int, umieścilibyśmy to w nagłówku z definicją wyliczenia:

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

Wszystkie poniższe elementy mogą znajdować się w różnych jednostkach kompilacji, przy założeniu, że powyższa część jest zawarta w nagłówku:

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

Przykładem tej techniki stosowanej w prawdziwym kodzie jest wysyłanie poleceń GPU w Chromium .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow