Недавно я изучаю некоторые продвинутые навыки программирования в метапрограммировании, и я столкнулся с проблемой понимания выполнения команд предварительной обработки в C++. Первоначально я предполагал, что команды предварительной обработки выполняются в том порядке, в котором они находятся в кодах C++. Однако, учитывая следующий код, он не дает ожидаемого результата.
#include <cstdio>
void a() {
printf("a\n");
}
void b() {
printf("b\n");
}
void c() {
printf("c\n");
}
int main() {
#define a b
#define c a
a(); // 1. a() -> b()
b();
c(); // 2. c() -> a()
#undef c
#undef a
return 0;
}
/*
if #define is executed in order, then expect
b
b
a
however get
b
b
b
*/
Я думал, что он сначала выполнит замену
#define a b
Производить
int main() {
b();
b();
c();
return 0;
}
Затем выполнить
#define c a
Производить
int main() {
b();
b();
a();
return 0;
}
Однако, когда я компилирую этот код и запускаю его в терминале, я получаю
b
b
b
Поэтому мне интересно, как именно понять процесс выполнения #define, а также других команд предварительной обработки.
Спасибо.
Препроцессор сканирует исходник только один раз, применяя все подходящие макросы. Он не выполняет отдельное сканирование для каждого макроса. Кроме того, порядок создания макросов не имеет значения, важно то, какие макросы существуют в той или иной строке.
Метапрограммирование обычно осуществляется не через макросы (которые не рекомендуется использовать в C++), а через шаблоны классов и функций. Я думаю, что все, что делает ваш код на данный момент, делает вещи более запутанными, чем они должны быть.
Препроцессор выполняется по порядку. В частности, он выполняется построчно. Некоторые строки являются директивами препроцессора (например, #define); другие нет. Когда препроцессор обрабатывает директиву #define, он делает это, добавляя это определение к своему набору определений макросов. Когда препроцессор обрабатывает обычную строку кода, он делает это (среди прочего) путем поиска токенов, соответствующих определениям макросов, и замены их заменой. Если сама замена включает имена макросов, эти макросы снова заменяются.
Поэтому, когда он встречает c();, он сначала использует определение макроса c для создания a();. Затем он использует определение макроса a для создания b();. Это не содержит имен макросов для замены, так что это сделано.
Обратите внимание, что порядок утверждений #define относительно друг друга не имеет значения. #define просто помещает записи в таблицу макросов. Они не действуют, пока не используются в обычных строках кода.
Файл сканируется один раз, и препроцессор c (CPP) имеет состояние, которое он поддерживает через строки. В любой строке порядок, в котором были определены существующие в данный момент макросы, не имеет значения:
#define A B // CPP notes down A->B
#define C A // CPP notes down C->A
A // Expands to B
B // Stays B
C // Expands to A, which again expands to B
#undef A // CPP removes the entry for A
#define A D // CPP notes down A->D
A // Expands to D
B // Stays B
C // Expands to A, which again expands to D
Смотрите дополнение онлайн
У вас есть пара замен, поэтому первый случай на самом деле c() -> a() -> b(). Также в целом я бы категорически избегал макросов, в большинстве случаев они ухудшают читабельность, и есть более идиоматические способы их использования.