Я всегда спрашивал об этом, но никогда не получал действительно хорошего ответа; Я думаю, что почти любой программист, даже до написания первого «Hello World», сталкивался с такими фразами, как «макрос никогда не должен использоваться», «макросы - это зло» и так далее. У меня вопрос: почему? Есть ли реальная альтернатива новому C ++ 11 по прошествии стольких лет?
Легкая часть связана с макросами, такими как #pragma
, которые зависят от платформы и компилятора, и в большинстве случаев они имеют серьезные недостатки, такие как #pragma once
подверженность ошибкам как минимум в двух важных ситуациях: одно и то же имя в разных путях и с некоторыми сетевыми настройками и файловыми системами.
А вообще, как насчет макросов и альтернатив их использованию?
источник
#pragma
это не макрос.#pragma
.constexpr
,inline
функциями, иtemplates
, ноboost.preprocessor
иchaos
показать , что макросы имеют свое место. Не говоря уже о макросах конфигурации для разных компиляторов, платформ и т. Д.Ответы:
Макросы такие же, как и любой другой инструмент: молоток, использованный при убийстве, не является злом, потому что это молоток. Это зло в том, как человек использует это таким образом. Если вы хотите забить гвозди, молоток - идеальный инструмент.
Есть несколько аспектов макросов, которые делают их «плохими» (я подробно остановлюсь на каждом позже и предложу альтернативы):
Итак, давайте здесь немного расширимся:
1) Макросы нельзя отлаживать. Когда у вас есть макрос, который переводится в число или строку, исходный код будет иметь имя макроса, и многие отладчики не могут «увидеть», во что переводится макрос. Так что вы на самом деле не знаете, что происходит.
Замена : используйте
enum
илиconst T
Для «функционально-подобных» макросов, поскольку отладчик работает на уровне «для каждой исходной строки, где вы находитесь», ваш макрос будет действовать как единый оператор, независимо от того, один это оператор или сотня. Сложно понять, что происходит.
Замена : используйте встроенные функции, если они должны быть "быстрыми" (но помните, что слишком много встроенных - это нехорошо)
2) Расширения макросов могут иметь странные побочные эффекты.
Знаменитый есть
#define SQUARE(x) ((x) * (x))
и пользаx2 = SQUARE(x++)
. Это приводит к томуx2 = (x++) * (x++);
, что, даже если бы это был действительный код [1], почти наверняка не было бы того, чего хотел программист. Если бы это была функция, было бы нормально выполнить x ++, и x увеличивался бы только один раз.Другой пример - "if else" в макросе, допустим, у нас есть это:
#define safe_divide(res, x, y) if (y != 0) res = x/y;
а потом
if (something) safe_divide(b, a, x); else printf("Something is not set...");
На самом деле это становится совершенно неправильным ...
Замена : реальные функции.
3) Макросы не имеют пространства имен
Если у нас есть макрос:
#define begin() x = 0
и у нас есть код на C ++, который использует begin:
std::vector<int> v; ... stuff is loaded into v ... for (std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it) std::cout << ' ' << *it;
Итак, какое сообщение об ошибке вы думаете, что получаете, и где искать ошибку [при условии, что вы полностью забыли - или даже не знали - макрос начала, который находится в каком-то файле заголовка, который написал кто-то другой? [и даже веселее, если вы включите этот макрос перед включением - вы утонете в странных ошибках, которые не имеют абсолютно никакого смысла, когда вы смотрите на сам код.
Замена : Что ж, здесь не так много замены, как «правило» - используйте только прописные имена для макросов и никогда не используйте все заглавные имена для других вещей.
4) Макросы имеют эффекты, о которых вы не подозреваете
Возьмите эту функцию:
#define begin() x = 0 #define end() x = 17 ... a few thousand lines of stuff here ... void dostuff() { int x = 7; begin(); ... more code using x ... printf("x=%d\n", x); end(); }
Теперь, не глядя на макрос, можно подумать, что begin - это функция, которая не должна влиять на x.
Подобные вещи, а я видел гораздо более сложные примеры, могут ДЕЙСТВИТЕЛЬНО испортить ваш день!
Замена : либо не используйте макрос для установки x, либо передайте x в качестве аргумента.
Бывают случаи, когда использование макросов определенно полезно. Один из примеров - обернуть функцию макросами для передачи информации о файле / строке:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__) #define free(x) my_debug_free(x, __FILE__, __LINE__)
Теперь мы можем использовать
my_debug_malloc
в коде как обычный malloc, но у него есть дополнительные аргументы, поэтому, когда дело доходит до конца и мы просканируем «какие элементы памяти не были освобождены», мы можем вывести, где было выполнено выделение, чтобы программист может отследить утечку.[1] Обновление одной переменной более одного раза «в точке последовательности» является неопределенным поведением. Точка последовательности - это не совсем то же самое, что оператор, но для большинства намерений и целей мы должны рассматривать это как. Таким образом,
x++ * x++
будет выполнено обновлениеx
дважды, что не определено и, вероятно, приведет к разным значениям в разных системах, а также к разному значению результатаx
.источник
if else
проблемы могут быть решены путем обертывания макро внутри телаdo { ... } while(0)
. Это ведет себя , как и следовало ожидать , поif
иfor
и другим вопросам , потенциально рискованные потока управления. Но да, настоящая функция обычно является лучшим решением.#define macro(arg1) do { int x = func(arg1); func2(x0); } while(0)
note: expanded from macro 'begin'
и покажут, гдеbegin
это определено.Поговорка «макросы - это зло» обычно относится к использованию #define, а не #pragma.
В частности, выражение относится к этим двум случаям:
определение магических чисел как макросов
использование макросов для замены выражений
Да, для элементов в списке выше (магические числа должны быть определены с помощью const / constexpr, а выражения должны быть определены с помощью функций [normal / inline / template / inline template].
Вот некоторые из проблем, возникающих из-за определения магических чисел как макросов и замены выражений макросами (вместо определения функций для вычисления этих выражений):
при определении макросов для магических чисел компилятор не сохраняет информацию о типе для определенных значений. Это может вызвать предупреждения (и ошибки) компиляции и сбить с толку людей, отлаживающих код.
при определении макросов вместо функций программисты, использующие этот код, ожидают, что они будут работать как функции, а они этого не делают.
Рассмотрим этот код:
#define max(a, b) ( ((a) > (b)) ? (a) : (b) ) int a = 5; int b = 4; int c = max(++a, b);
Вы ожидаете, что a и c будут равны 6 после присвоения c (как и при использовании std :: max вместо макроса). Вместо этого код выполняет:
int c = ( ((++a) ? (b)) ? (++a) : (b) ); // after this, c = a = 7
Кроме того, макросы не поддерживают пространства имен, а это означает, что определение макросов в вашем коде ограничит клиентский код тем, какие имена они могут использовать.
Это означает, что если вы определите макрос выше (для max), вы больше не сможете использовать
#include <algorithm>
ни один из приведенных ниже кодов, если вы явно не напишете:#ifdef max #undef max #endif #include <algorithm>
Наличие макросов вместо переменных / функций также означает, что вы не можете получить их адрес:
если макрос как константа оценивается как магическое число, вы не можете передать его по адресу
для макроса как функции вы не можете использовать его в качестве предиката, принимать адрес функции или рассматривать ее как функтор.
Изменить: в качестве примера правильная альтернатива приведенному
#define max
выше:template<typename T> inline T max(const T& a, const T& b) { return a > b ? a : b; }
Это делает все, что делает макрос, с одним ограничением: если типы аргументов различаются, версия шаблона заставляет вас быть явным (что на самом деле приводит к более безопасному и более явному коду):
int a = 0; double b = 1.; max(a, b);
Если этот максимум определен как макрос, код будет компилироваться (с предупреждением).
Если этот max определен как функция шаблона, компилятор укажет на двусмысленность, и вы должны будете сказать либо
max<int>(a, b)
илиmax<double>(a, b)
(и, таким образом, явно заявить о своем намерении).источник
const int someconstant = 437;
макроса, и его можно использовать почти во всех случаях. То же самое и для небольших функций. Есть несколько вещей, в которых вы можете написать что-то в виде макроса, который не будет работать в регулярном выражении в C (вы можете сделать что-то, что усредняет массив любого типа чисел, что C не может сделать, но у C ++ есть шаблоны для этого). В то время как C ++ 11 добавляет еще несколько вещей, которые «для этого вам не нужны макросы», в основном это уже решено в более ранних версиях C / C ++.max
аmin
если за ними следуют левая скобка. Но не стоит определять такие макросы ...Распространенная проблема такая:
#define DIV(a,b) a / b printf("25 / (3+2) = %d", DIV(25,3+2));
Он напечатает 10, а не 5, потому что препроцессор расширит его следующим образом:
printf("25 / (3+2) = %d", 25 / 3 + 2);
Эта версия более безопасна:
#define DIV(a,b) (a) / (b)
источник
DIV
Макрос может быть переписан с парой () вокругb
.#define DIV(a,b)
, что нет#define DIV (a,b)
, это совсем другое.#define DIV(a,b) (a) / (b)
недостаточно хорошо; как правило, всегда добавляйте крайние скобки, например:#define DIV(a,b) ( (a) / (b) )
Макросы особенно ценны для создания универсального кода (параметры макроса могут быть любыми), иногда с параметрами.
Более того, этот код помещается (т.е. вставляется) в точку использования макроса.
OTOH, аналогичные результаты могут быть получены с помощью:
перегруженные функции (разные типы параметров)
шаблоны в C ++ (общие типы и значения параметров)
встроенные функции (поместите код там, где они вызываются, вместо перехода к одноточечному определению - однако это скорее рекомендация для компилятора).
изменить: что касается того, почему макрос плохой:
1) без проверки типов аргументов (у них нет типа), поэтому их можно легко использовать неправильно 2) иногда превращать в очень сложный код, который может быть трудно идентифицировать и понять в предварительно обработанном файле 3) легко сделать ошибку код в макросах, например:
#define MULTIPLY(a,b) a*b
а потом позвони
MULTIPLY(2+3,4+5)
что расширяется в
2 + 3 * 4 + 5 (а не в: (2 + 3) * (4 + 5)).
Чтобы иметь последнее, вы должны определить:
#define MULTIPLY(a,b) ((a)*(b))
источник
Я не думаю, что есть что-то плохое в использовании определений препроцессора или макросов, как вы их называете.
Это (мета) языковая концепция, найденная в c / c ++, и, как и любой другой инструмент, они могут облегчить вашу жизнь, если вы знаете, что делаете. Проблема с макросами заключается в том, что они обрабатываются до вашего кода c / c ++ и генерируют новый код, который может быть ошибочным и вызывать ошибки компилятора, которые почти очевидны. С другой стороны, они могут помочь вам сохранить ваш код в чистоте и сэкономить много времени на вводе при правильном использовании, так что все сводится к личным предпочтениям.
источник
Макросы в C / C ++ могут служить важным инструментом для контроля версий. Один и тот же код может быть доставлен двум клиентам с незначительной настройкой макросов. Я использую такие вещи как
#define IBM_AS_CLIENT #ifdef IBM_AS_CLIENT #define SOME_VALUE1 X #define SOME_VALUE2 Y #else #define SOME_VALUE1 P #define SOME_VALUE2 Q #endif
Такая функциональность не так просто возможна без макросов. На самом деле макросы - это отличный инструмент управления конфигурацией программного обеспечения, а не просто способ создания ярлыков для повторного использования кода. Определение функций с целью повторного использования в макросах определенно может создать проблемы.
источник
Я думаю, что проблема в том, что макросы плохо оптимизированы компилятором и «некрасивы» для чтения и отладки.
Часто хорошей альтернативой являются общие функции и / или встроенные функции.
источник
Макросы препроцессора не являются злом, когда они используются по назначению, например:
Альтернативы. Для аналогичных целей можно использовать какие-то файлы конфигурации в формате ini, xml, json. Но их использование повлияет на время выполнения кода, чего можно избежать с помощью макроса препроцессора.
источник