// вызвать f, передав ей максимум из a и b
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))
В этой строчке содержится так много недостатков, что даже не совсем понятно, с какого начать.
Всякий раз при написании подобного макроса вы должны помнить о том, что все аргументы следует заключать в скобки. В противном случае вы рискуете столкнуться с проблемой, когда кто-нибудь вызовет его с выражением в качестве аргумента. Но даже если вы сделаете все правильно, посмотрите, какие странные вещи могут произойти:
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a увеличивается дважды
CALL_WITH_MAX(++a, b+10); // a увеличивается один раз
Происходящее внутри max зависит от того, с чем она сравнивается!
К счастью, вы нет нужды мириться с поведением, так сильно противоречащим привычной логике. Существует метод, позволяющий добиться такой же эффективности, как при использовании препроцессора. Но при этом обеспечивается как предсказуемость поведения, так и контроль типов аргументов (что характерно для обычных функций). Этот результат достигается применением шаблона встроенной (inline) функции (см. правило 30):
template
inline void callWithMax(const T& a, const T& b) // Поскольку мы не знаем,
{ // что есть T, то передаем
f(a > b ? a : b); // его по ссылке на const -
} // см. параграф 20
Этот шаблон генерирует целое семейство функций, каждая из которых принимает два аргумента одного и того же типа и вызывает f с наибольшим из них. Нет необходимости заключать параметры в скобки внутри тела функции, не нужно заботиться о многократном вычислении параметров и т. д. Более того, поскольку callWithMax – настоящая функция, на нее распространяются правила областей действия и контроля доступа. Например, можно говорить о встроенной функции, являющейся закрытым членом класса. Описать нечто подобное с помощью макроса невозможно.
Наличие const, enum и inline резко снижает потребность в препроцессоре (особенно это относится к #define), но не устраняет ее полностью. Директива #include остается существенной, а #ifdef/#ifndef продолжают играть важную роль в управлении компиляцией. Пока еще не время отказываться от препроцессора, но определенно стоит задуматься, как избавиться от него в дальнейшем.
Что следует помнить
• Для простых констант директиве #define следует предпочесть константные объекты и перечисления (enum).
• Вместо имитирующих функции макросов, определенных через #define, лучше применять встроенные функции.
Правило 3: Везде, где только можно используйте const
Замечательное свойство модификатора const состоит в том, что он накладывает определенное семантическое ограничение: данный объект не должен модифицироваться, – и компилятор будет проводить это ограничение в жизнь. const позволяет указать компилятору и программистам, что определенная величина должна оставаться неизменной. Во всех подобных случаях вы должны обозначить это явным образом, призывая себе на помощь компилятор и гарантируя тем самым, что ограничение не будет нарушено.
Ключевое слово const удивительно многосторонне. Вне классов вы можете использовать его для определения констант в глобальной области или в пространстве имен (см. правило 2), а также для статических объектов (внутри файла, функции или блока). Внутри классов допустимо применять его как для статических, так и для нестатических данных-членов. Для указателей можно специфицировать, должен ли быть константным сам указатель, данные, на которые он указывает, либо и то, и другое (или ни то, ни другое):
char greeting[] = “Hello”;
char *p = greeting; // неконстантный указатель,
// неконстантные данные
const char *p = greeting; // неконстантный указатель,
// константные данные
char * const p = greeting; // константный указатель,
// неконстантные данные
const char * const p = greeting; // константный указатель,
// константные данные
Этот синтаксис не так страшен, как может показаться. Если слово const появляется слева от звездочки, константным является то, на что указывает указатель; если справа, то сам указатель является константным. Наконец, если же слово const появляется с обеих сторон, то константно и то, и другое.
Когда то, на что указывается, – константа, некоторые программисты ставят const перед идентификатором типа. Другие – после идентификатора типа, но перед звездочкой. Семантической разницы здесь нет, поэтому следующие функции принимают параметр одного и того же типа:
Читать дальше
Конец ознакомительного отрывка
Купить книгу