C Language
X-макросы
Поиск…
Вступление
X-макросы - это метод на основе препроцессора для минимизации повторяющегося кода и поддержания соответствия данных / кода. Множественные отличия макросов, основанные на общем наборе данных, поддерживаются путем представления всей группы расширений с помощью одного главного макроса с заменяющим текстом этого макроса, состоящим из последовательности расширений внутреннего макроса, по одному для каждой базы данных. Внутренний макрос традиционно называется X()
, отсюда и название техники.
замечания
Предполагается, что пользователь основного макроса в стиле X-макроса предоставит свое собственное определение для внутреннего макроса X()
и в пределах его возможностей для расширения основного макроса. Таким образом, внутренние макро-ссылки мастера расширяются в соответствии с определением пользователя X()
. Таким образом, количество повторяющегося кода шаблона в исходном файле может быть уменьшено (появляется только один раз, в заменяющем тексте X()
), как это предпочитают сторонники философии «Не повторяйте себя» (DRY).
Кроме того, переопределяя X()
и расширяя мастер-макрос один или несколько дополнительных раз, макросы X могут облегчить сохранение соответствующих данных и кода - одно расширение макроса объявляет данные (например, как элементы массива или элементы перечисления), и другие расширения производят соответствующий код.
Хотя имя «X-macro» происходит от традиционного имени внутреннего макроса, этот метод не зависит от этого конкретного имени. Любое допустимое имя макроса можно использовать на своем месте.
Критики включают
- исходные файлы, которые полагаются на макросы X, труднее читать;
- как и все макросы, макросы X строго текстовые - они по своей сути не обеспечивают безопасность любого типа; а также
- X для генерации кода . По сравнению с альтернативами, основанными на вызывающих функциях, макросы X эффективно делают код более крупным.
Хорошее объяснение макросов X можно найти в статье Рэнди Майерса [X-Macros] у доктора Доббса ( http://www.drdobbs.com/the-new-cx-macros/184401387) .
Тривиальное использование X-макросов для 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 */
В этом примере препроцессор генерирует следующий код:
printf("X(%d) made this print\n", 1);
printf("X(%d) made this print\n", 2);
printf("X(%d) made this print\n", 3);
Значение и идентификатор Enum
/* 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;
}
Затем вы можете использовать перечисленное значение в своем коде и легко распечатать его идентификатор, используя:
printf("%s\n", enum2string(MyEnum_item2));
Расширение: укажите макрос X как аргумент
Метод X-macro можно обобщить, указав имя макроса «X» как аргумент главного макроса. Это дает преимущества, позволяющие избежать конфликтов имен макросов и разрешить использование макроса общего назначения в качестве макроса «X».
Как всегда с макросами X, главный макрос представляет список элементов, значение которых специфично для этого макроса. В этом варианте такой макрос может быть определен следующим образом:
/* declare list of items */
#define ITEM_LIST(X) \
X(item1) \
X(item2) \
X(item3) \
/* end of list */
Затем можно сгенерировать код для печати имен элементов следующим образом:
/* define macro to apply */
#define PRINTSTRING(value) printf( #value "\n");
/* apply macro to the list of items */
ITEM_LIST(PRINTSTRING)
Это расширяется до этого кода:
printf( "item1" "\n"); printf( "item2" "\n"); printf( "item3" "\n");
В отличие от стандартных макросов X, где «X» - это встроенная характеристика главного макроса, с этим стилем может быть ненужным или даже нежелательным после этого определять неопределенный макрос, используемый в качестве аргумента ( PRINTSTRING
в этом примере).
Генерация кода
X-Macros можно использовать для генерации кода, написав повторяющийся код: перебирать список для выполнения некоторых задач или объявлять набор констант, объектов или функций.
Здесь мы используем X-макросы для объявления перечисления, содержащего 4 команды, и карту их имен в виде строк
Затем мы можем напечатать строковые значения перечисления.
/* 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]());
Аналогично, мы можем создать таблицу переходов для вызова функций по значению перечисления.
Для этого требуется, чтобы все функции имели одну и ту же подпись. Если они не принимают аргументов и возвращают int, мы помещаем это в заголовок с определением перечисления:
/* 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[];
Все перечисленные ниже могут быть в разных единицах компиляции, если указанная выше часть включена в заголовок:
/* 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 */}
Примером этого метода, используемого в реальном коде, является диспетчеризация графических процессоров в Chromium .