Язык программирования C++ для профессионалов

       

Макросредства


В С++ макросредства играют гораздо меньшую роль, чем в С. Можно даже дать такой совет: используйте макроопределения только тогда, когда не можете без них обойтись. Вообще говоря, считается, что практически каждое появление макроимени является свидетельством некоторых недостатков языка, программы или программиста. Макросредства создают определенные трудности для работы служебных системных программ, поскольку они перерабатывают программный текст еще до трансляции. Поэтому, если ваша программа использует макросредства, то сервис, предоставляемый такими программами, как отладчик, профилировщик, программа перекрестных ссылок, будет для нее неполным. Если все-таки вы решите использовать макрокоманды, то вначале тщательно изучите описание препроцессора С++ в вашем справочном руководстве и не старайтесь быть слишком умным.

Простое макроопределение имеет вид:

#define имя остаток-строки

В тексте программы лексема имя заменяется на остаток-строки. Например,

объект = имя

будет заменено на

объект = остаток-строки

Макроопределение может иметь параметры. Например:

#define mac(a,b) argument1: a argument2: b

В макровызове mac должны быть заданы две строки, представляющие параметры. При подстановке они заменят a и b в макроопределении mac(). Поэтому строка

expanded = mac(foo bar, yuk yuk)

при подстановке преобразуется в

expanded = argument1: foo bar argument2: yuk yuk

Макроимена нельзя перегружать. Рекурсивные макровызовы ставят перед препроцессором слишком сложную задачу:

// ошибка: #define print(a,b) cout<<(a)<<(b) #define print(a,b,c) cout<<(a)<<(b)<<(c)

// слишком сложно: #define fac(n) (n>1) ?n*fac(n-1) :1



Препроцессор работает со строками и практически ничего не знает о синтаксисе C++, типах языка и областях видимости. Транслятор имеет дело только с уже раскрытым макроопределением, поэтому ошибка в нем может диагностироваться уже после подстановки, а не при определении макроимени. В результате появляются довольно путанные сообщения об ошибках.

Допустимы такие макроопределения:


#define Case break;case #define forever for(;;)

А вот совершенно излишние макроопределения:

#define PI 3.141593 #define BEGIN { #define END }

Следующие макроопределения могут привести к ошибкам:

#define SQUARE(a) a*a #define INCR_xx (xx)++ #define DISP = 4

Чтобы убедиться в этом, достаточно попробовать сделать подстановку в таком примере:

int xx = 0;// глобальный счетчик

void f() { int xx = 0;// локальная переменная xx = SQUARE(xx+2); // xx = xx +2*xx+2; INCR_xx;// увеличивается локальная переменная xx if (a-DISP==b) { // a-=4==b // ... } }

При ссылке на глобальные имена в макроопределении используйте операцию разрешения области видимости, и всюду, где это возможно, заключайте имя параметра макроопределения в скобки. Например:

#define MIN(a,b) (((a)<(b))?(a):(b))

Если макроопределение достаточно сложное, и требуется комментарий к нему, то разумнее написать комментарий вида /* */, поскольку в реализации С++ может использоваться препроцессор С, который не распознает комментарии вида //. Например:

#define m2(a) something(a) /* глубокомысленный комментарий */

С помощью макросредств можно создать свой собственный язык, правда, скорее всего, он будет непонятен другим. Кроме того, препроцессор С предоставляет довольно слабые макросредства. Если ваша задача нетривиальна, вы, скорее всего, обнаружите, что решить ее с помощью этих средств либо невозможно, либо чрезвычайно трудно. В качестве альтернативы традиционному использованию макросредств в язык введены конструкции const, inline и шаблоны типов. Например:

const int answer = 42; template<class T> inline T min(T a, T b) { return (a<b)?a:b; }

Содержание раздела