Макросы такие же, как и любой другой инструмент: молоток, использованный при убийстве, не является злом, потому что это молоток. Это зло в том, как человек использует это таким образом. Если вы хотите забить гвозди, молоток - идеальный инструмент.
Есть несколько аспектов макросов, которые делают их «плохими» (я подробно остановлюсь на каждом позже и предложу альтернативы):
- Вы не можете отлаживать макросы.
- Расширение макросов может привести к странным побочным эффектам.
- Макросы не имеют «пространства имен», поэтому, если у вас есть макрос, который конфликтует с именем, используемым в другом месте, вы получаете замену макроса там, где вы этого не хотели, и это обычно приводит к странным сообщениям об ошибках.
- Макросы могут повлиять на то, чего вы не понимаете.
Итак, давайте здесь немного расширимся:
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
.
#pragma
это не макрос.