Вот как выглядит мой макрос #define
:
#define CUSTOM_COMPARATOR(table, column, a, b) \
do { \
const struct db_##table *row1 = a; \
const struct db_##table *row2 = b; \
\
/* Insert lot of code here with multiple row->##column usage */ \
return strcmp(row1->##column->name1, row2->##column->name2); \
} while (0)
Мне нужно создать несколько определений на основе разных типов таблиц и столбцов. Эти определения предварительно обрабатываются и вызываются в нескольких функциях:
static int
db_table_sometable_somecolumn_comparator(const void *a, const void *b) {
CUSTOM_COMPARATOR(sometable, somecolumn, a, b);
}
static int
db_table_someothertable_someothercolumn_comparator(const void *a, const void *b) {
CUSTOM_COMPARATOR(someothertable, someothercolumn, a, b);
}
По сути, я хочу избежать дублирования кода внутри CUSTOM_COMPARATOR
. Когда я компилирую этот gcc, он жалуется, что pasting "->" and "somecolumn" does not give a valid preprocessing token
, что понятно, поскольку ->
ломает токен. Он работает на db_##table
, потому что производит один токен.
Тем не менее, есть ли способ добиться этого? У меня есть около 10 таблиц/столбцов с простым изменением имени, но я использую ту же логику, что и в CUSTOM_COMPARATOR
, что в действительности также составляет 50 LoC.
Вы правы, на эту фразу больно смотреть. Это действительно просто name
. Я опечатал его, пытаясь переименовать производственный код.
Почему бы и нет #define GENERATE_COMPARATOR(table, column) static int db_table_ ## table ## _ ## column ## _comparator(const void *a, const void *b) { CUSTOM_COMPARATOR(table, column, a, b) }
? Это снижает риск несоответствия имени функции и кода компаратора.
И вам может понадобиться #define COMPARATOR_NAME(table, column) db_table_ ## table ## _ ## column ## _comparator
для создания имени компаратора. Вам следует использовать это в определении GENERATE_COMPARATOR
, но вы также можете использовать его при создании имени для передачи в код сортировки: qsort(rows, number, sizeof(struct db_sometable), COMPARATOR_NAME(sometable, somecolumn));
и т. д. Вы можете даже беспокоиться о заблокированном sometable
в db_sometable
. Вы можете сойти с ума, если не будете осторожны.
Вставку токенов следует рассматривать как дополнительную функцию. Вы можете написать множество полезных макросов без вставки токенов. Если вы планируете использовать его, помните, что полезная вставка токена почти всегда принимает форму соединения двух частей вместе для формирования идентификатора (имени). Если результат вставки не предназначен для использования в качестве идентификатора, то, вероятно, вы не хотите выполнять эту вставку.
Не пытайтесь вставить имя столбца, чтобы создать новый токен. Вы можете просто сделать это:
return strcmp(row1->column->name1, row2->column->name2);
Это принимается препроцессором и расширяется, например, до с gcc -E
:
static int
db_table_sometable_somecolumn_comparator(const void *a, const void *b) {
do { const struct db_sometable *row1 = a; const struct db_sometable *row2 = b; return strcmp(row1->somecolumn->name1, row2->somecolumn->name2); } while (0);
}
static int
db_table_someothertable_someothercolumn_comparator(const void *a, const void *b) {
do { const struct db_someothertable *row1 = a; const struct db_someothertable *row2 = b; return strcmp(row1->someothercolumn->name1, row2->someothercolumn->name2); } while (0);
}
что совершенно справедливо C.
Это работает. Большое спасибо!
Меня удивляет, что в структуре
name1
есть два члена,name2
иstruct db_tablename
. Я ожидаю, что вы будете использовать один столбецname
.