Трудно найти язык, достаточно красочный, чтобы выразить все, что хочется сказать о макросах. Но тем не менее приведем несколько цитат.
Макросы по многим причинам — весьма неприятная вещь, которая может стать попросту опасной. В первую очередь это связано с тем, что макросы — средство замены текста, действующее во время обработки исходного текста препроцессором, т.е. еще до того, как начнется какая-либо проверка синтаксиса и семантики.
Мне не нравится большинство видов препроцессоров и макросов. Одна из целей С++ — сделать препроцессор С излишним (§4.4, §18), поскольку я считаю его большой ошибкой
Макросы почти никогда не являются необходимыми в С++. Используйте const
(§5.4) или enum
(§4.8) для определения явных констант inline
(§7.1.1) для того, чтобы избежать накладных расходов на вызов функции [но см. рекомендацию 8], template
(глава 13) для определения семейств функций и типов namespace
(§8.2) для того, чтобы избежать конфликтов имен
Первое правило по применению макросов гласит: не используйте их до тех пор, пока у вас не будет другого выхода. Практически любой макрос свидетельствует о несовершенстве языка программирования, программы или программиста.
Основная проблема с макросами С++ заключается в том, что они выглядят гораздо привлекательнее, чем являются таковыми на самом деле. Макросы игнорируют области видимости, игнорируют прочие возможности и правила языка, и заменяют все символы, которые переопределяют при помощи директивы #define
, до самого конца файла. Применение макросов внешне походит на имя или вызов функции, но не имеет с ними ничего общего. Макросы 'негигиеничны', в том смысле, что они могут быть раскрыты неожиданно, причем превратиться в зависимости от контекста их использования в самые разные конструкции. Подстановка текста, выполняемая макросами, делает написание хотя бы в небольшой степени 'приличного' макроса смесью искусства и черной магии.
Программисты, которые полагают, что тяжелее всего расшифровать ошибки, связанные с шаблонами, вероятно, просто никогда не имели дела с плохо написанными или неверно использованными макросами. Шаблоны являются частью системы типов С++, и тем самым позволяют компилятору куда лучше справляться с ними, чем с макросами, которые имеют мало общего с языком программирования. Хуже того, в отличие от шаблонов неверные макросы могут быть раскрыты в нечто, что в силу чистой случайности скомпилируется, не имея при этом никакого смысла. И наконец, ошибка в макросе обнаруживается только после того, как макрос раскрывается, а не при его определении.
Даже в тех редких случаях, где применение макросов оправданно (см. подраздел, посвященный исключениям), нельзя даже подумать о том, чтобы написать макрос, который является распространенным словом или аббревиатурой. Для всех макросов как можно скорее применяйте директиву #undef
, всегда давая им необычные уродливые имена в верхнем регистре, избегая при этом размещения их в заголовочных файлах.
macro(Foo<int, double>)
макрос воспринимает так, будто ему переданы два аргумента, а именно Foo<int
и doublе>
, в то время как в действительности эта конструкция представляет собой единый объект С++.
Макросы остаются единственно возможным решением для некоторых важных задач, таких как защита директивы #include
(см. рекомендацию 24), использование директив #ifdef
и #if defined
для условной компиляции и реализация assert
(см. рекомендацию 68).
При условной компиляции (например, системно-зависимых частей) избегайте разброса по всему тексту директив #ifdef
. Вместо этого лучше организовать код таким образом, чтобы использование макросов обеспечивало возможность альтернативных реализаций одного общего интерфейса, который затем будет использоваться в программе.
Можно (но осторожно) использовать макросы вместо большого количества копирований и вставок близких фрагментов кода.
Заметим, что [C99] и [Boost] включают соответственно умеренные и радикальные расширения препроцессоров.
17. Избегайте магических чисел
Избегайте использования в коде литеральных констант наподобие 42 или 3.1415926. Такие константы не самоочевидны и усложняют сопровождение кода, поскольку вносят в него трудноопределимый вид дублирования. Используйте вместо них символьные имена и выражения наподобие width*aspectRatiо
.
Имена добавляют информацию и вводят единую точку сопровождения; в отличие от них дублированные по всей программе обычные числа анонимны и трудно сопровождаемы. Константы должны быть перечислениями или const
-значениями, с соответствующими областями видимости и именами.
Одно число 42 может не быть тем же числом 42, что и другое. Что еще хуже, программист может выполнять какие-то вычисления 'в уме' (например: 'Вот это 84 — просто удвоенное 42, которое было пятью строками ранее'), что совершенно запутывает код и делает последующую замену 42 другой константой источником огромного количества ошибок.
Лучше заменять такие жестко кодированные величины символьными константами. Строки лучше хранить отдельно от кода (например, в отдельном .срр
-файле или файле ресурса), что позволит непрограммистам просмотреть и обновить их, снижая количество дубликатов и помогая в интернационализации вашей программы.