Допустим, вам по какой-то причине нужно написать макрос: MACRO(X,Y). (Предположим, есть веская причина, по которой вы не можете использовать встроенную функцию.) Вы хотите, чтобы этот макрос имитировал вызов функции без возвращаемого значения.
if (x > y)
MACRO(x, y);
do_something();
if (x > y)
MACRO(x, y);
else
MACRO(y - x, x - y);
do_something();
MACRO(x, y)
do_something();
Наивный способ написать макрос выглядит так:
#define MACRO(X,Y) \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl;
Это очень плохое решение, которое не подходит для всех трех примеров, и мне не нужно объяснять, почему.
Не обращайте внимания на то, что на самом деле делает макрос, дело не в этом.
Чаще всего макросы заключаются в фигурные скобки, например:
#define MACRO(X,Y) \
{ \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl; \
}
Это решает пример 1, потому что макрос находится в одном блоке операторов. Но пример 2 не работает, потому что мы ставим точку с запятой после вызова макроса. Это заставляет компилятор думать, что точка с запятой сама по себе является оператором, что означает, что оператор else не соответствует ни одному оператору if! И, наконец, пример 3 компилируется нормально, хотя точки с запятой нет, потому что для блока кода точка с запятой не нужна.
Есть ли способ написать макрос, чтобы он передавал все три примера?
Примечание: я отправляю свой собственный ответ как часть приемлемый способ поделиться советом, но если у кого-то есть лучшее решение, не стесняйтесь размещать его здесь, оно может получить больше голосов, чем мой метод. :)
Макросы безопасны по типу нет. Вот почему вы должны вместо этого выбрать встроенные функции, которыми являются.
@ b1naryatr0phy Я думаю, что беспорядок макросов больше проблема, почему не макросы.





Есть довольно умное решение:
#define MACRO(X,Y) \
do { \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl; \
} while (0)
Теперь у вас есть один оператор уровня блока, за которым должна стоять точка с запятой. Это ведет себя так, как ожидалось и желательно во всех трех примерах.
Да, все мы знаем подводные камни вызова макроса со сложными аргументами. Вот почему все называют (или должны!) Макросы заглавными буквами, чтобы вы знали, что нельзя делать глупостей.
Между прочим, проблема множественной оценки не является непреодолимой. Проверьте этот код, который сделал умный парень -> pastie.org/357910 Он предоставляет надежный макрос «RANGE (foo)», который в основном генерирует «foo.begin (), foo.end ()», но он применим для решения любой множественной оценки. Зато накладные расходы времени выполнения.
Создайте блок, используя
#define MACRO(...) do { ... } while(false)
Не добавляйте; через некоторое время (ложь)
Если вы готовы принять практику использования фигурных скобок в операторах if,
В вашем макросе просто не будет последней точки с запятой:
#define MACRO(X,Y) \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl
Пример 1: (компилируется)
if (x > y) {
MACRO(x, y);
}
do_something();
Пример 2: (компилируется)
if (x > y) {
MACRO(x, y);
} else {
MACRO(y - x, x - y);
}
Пример 3: (не компилируется)
do_something();
MACRO(x, y)
do_something();
Как правило, следует избегать макросов; всегда предпочитайте им встроенные функции. Любой достойный компилятор должен быть способен встраивать небольшую функцию, как если бы это был макрос, а встроенная функция будет учитывать пространства имен и другие области, а также оценивать все аргументы один раз.
Если это должен быть макрос, цикл while (уже предложенный) будет работать, или вы можете попробовать оператор запятой:
#define MACRO(X,Y) \
( \
(cout << "1st arg is:" << (X) << endl), \
(cout << "2nd arg is:" << (Y) << endl), \
(cout << "3rd arg is:" << ((X) + (Y)) << endl), \
(void)0 \
)
(void)0 заставляет оператор оценивать один из типов void, а использование запятых, а не точек с запятой позволяет использовать его внутри оператора, а не только как отдельный. Я бы по-прежнему рекомендовал встроенную функцию по множеству причин, наименьшая из которых - это область действия и тот факт, что MACRO(a++, b++) будет увеличивать a и b вдвое.
Отлично, это даже лучше, чем то, что я делал изначально! Спасибо!
Функции также превосходят то, что компилятор может решить, встроить их или нет, тогда как у него нет выбора с макросами. Встраивание очень часто не дает ожидаемого выигрыша в производительности.
«Отлично, это даже лучше, чем то, что я делал изначально!» - Ты ошибаешься, Кип, твой ответ лучше, чем этот. Ваш может вести себя как другие функции, чем этот, например, если ему нужен цикл в середине.
Кип, твоя версия лучше. Если вам нужно объявить некоторые локальные переменные в вашем макросе, объявление их внутри блока while не приведет к утечке их в вызывающую область.
Однако этот подход полезен, если вам нужно выполнить оператор, а затем вернуть значение. Например, я использовал его, чтобы установить барьер памяти перед загрузкой с приобретать семантику.
Ваш ответ страдает от проблемы множественной оценки, поэтому (например)
macro( read_int(file1), read_int(file2) );
сделает что-то неожиданное и, вероятно, нежелательное.
Как уже упоминали другие, вам следует по возможности избегать макросов. Они опасны при наличии побочных эффектов, если аргументы макроса оцениваются более одного раза. Если вам известен тип аргументов (или вы можете использовать функцию C++ 0x auto), вы можете использовать временные файлы для принудительной однократной оценки.
Еще одна проблема: порядок, в котором выполняются несколько оценок, может быть не таким, как вы ожидаете!
Рассмотрим этот код:
#include <iostream>
using namespace std;
int foo( int & i ) { return i *= 10; }
int bar( int & i ) { return i *= 100; }
#define BADMACRO( X, Y ) do { \
cout << "X = " << (X) << ", Y = " << (Y) << ", X+Y = " << ((X)+(Y)) << endl; \
} while (0)
#define MACRO( X, Y ) do { \
int x = X; int y = Y; \
cout << "X = " << x << ", Y = " << y << ", X+Y = " << ( x + y ) << endl; \
} while (0)
int main() {
int a = 1; int b = 1;
BADMACRO( foo(a), bar(b) );
a = 1; b = 1;
MACRO( foo(a), bar(b) );
return 0;
}
И он выводится как скомпилированный и запускаемый на моей машине:
X=100, Y=10000, X+Y=110 X=10, Y=100, X+Y=110
Проблема с вашим хорошим макросом в том, что вы предполагаете, что X и Y являются int. Они могли быть кем угодно. Вам нужно будет создать шаблон макроса или что-то в этом роде.
Примечание: выходы на Godbolt не соответствуют тем, которые указаны в ответе: godbolt.org/z/9fQuA9
Я знаю, что вы сказали «игнорировать то, что делает макрос», но люди найдут этот вопрос, выполнив поиск по названию, поэтому я думаю, что обсуждение дальнейших методов эмуляции функций с помощью макросов оправдано.
Ближайшее, что я знаю, это:
#define MACRO(X,Y) \
do { \
auto MACRO_tmp_1 = (X); \
auto MACRO_tmp_2 = (Y); \
using std::cout; \
using std::endl; \
cout << "1st arg is:" << (MACRO_tmp_1) << endl; \
cout << "2nd arg is:" << (MACRO_tmp_2) << endl; \
cout << "Sum is:" << (MACRO_tmp_1 + MACRO_tmp_2) << endl; \
} while(0)
Это делает следующее:
Однако он по-прежнему отличается от функции тем, что:
Вот ответ прямо от libc6!
Взглянув на /usr/include/x86_64-linux-gnu/bits/byteswap.h, я нашел уловку, которую вы искали.
Несколько критиков предыдущих решений:
auto, это нормально, но вместо этого используется не стесняйтесь использовать известный / ожидаемый тип.Уловка состоит в том, чтобы использовать как конструкцию (expr,expr), так и область видимости {}:
#define MACRO(X,Y) \
( \
{ \
register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \
std::cout << "1st arg is:" << __x << std::endl; \
std::cout << "2nd arg is:" << __y << std::endl; \
std::cout << "Sum is:" << (__x + __y) << std::endl; \
__x + __y; \
} \
)
Обратите внимание на использование ключевого слова register, это всего лишь подсказка для компилятора.
Параметры макроса X и Y (уже) заключены в круглые скобки и отлитый для ожидаемого типа.
Это решение правильно работает с пре- и пост-инкрементом, поскольку параметры оцениваются только один раз.
В качестве примера, даже если он не запрошен, я добавил оператор __x + __y;, который позволяет оценить весь блок как это точное выражение.
Безопаснее использовать void();, если вы хотите быть уверенным, что макрос не будет вычислять выражение, что является недопустимым там, где ожидается rvalue.
тем не мение, решение - не соответствует ISO C++, как будет жаловаться g++ -pedantic:
warning: ISO C++ forbids braced-groups within expressions [-pedantic]
Чтобы дать g++ немного отдохнуть, используйте (__extension__ OLD_WHOLE_MACRO_CONTENT_HERE), чтобы новое определение гласило:
#define MACRO(X,Y) \
(__extension__ ( \
{ \
register int __x = static_cast<int>(X), __y = static_cast<int>(Y); \
std::cout << "1st arg is:" << __x << std::endl; \
std::cout << "2nd arg is:" << __y << std::endl; \
std::cout << "Sum is:" << (__x + __y) << std::endl; \
__x + __y; \
} \
))
Чтобы еще немного улучшить мое решение, давайте воспользуемся ключевым словом __typeof__, как показано в МИН и МАКС в C:
#define MACRO(X,Y) \
(__extension__ ( \
{ \
__typeof__(X) __x = (X); \
__typeof__(Y) __y = (Y); \
std::cout << "1st arg is:" << __x << std::endl; \
std::cout << "2nd arg is:" << __y << std::endl; \
std::cout << "Sum is:" << (__x + __y) << std::endl; \
__x + __y; \
} \
))
Теперь компилятор определит подходящий тип. Это тоже расширение gcc.
Обратите внимание на удаление ключевого слова register, так как это будет следующее предупреждение при использовании с типом класса:
warning: address requested for ‘__x’, which is declared ‘register’ [-Wextra]
В C++ 11 появились лямбды, которые могут быть невероятно полезны в этой ситуации:
#define MACRO(X,Y) \
[&](x_, y_) { \
cout << "1st arg is:" << x_ << endl; \
cout << "2nd arg is:" << y_ << endl; \
cout << "Sum is:" << (x_ + y_) << endl; \
}((X), (Y))
Вы сохраняете генерирующую силу макросов, но имеете удобную область видимости, из которой вы можете вернуть все, что захотите (включая void). Кроме того, устраняется проблема многократной оценки параметров макроса.
См. Также: Почему в макросах C / C++ иногда встречаются бессмысленные операторы do / while и if / else?