Приветствую Вас ГостьВоскресенье, 13.07.2025, 03:12

Light Midnight Inc.


Каталог статей

Главная » Статьи » Программирование » С/С++/MFC

Макроопределения и их недостатки

Использовать макроопределения иногда удобно. Во многих случаях, конечно, стоит предпочесть использование параметризованных функций (шаблонов) и других механизмов, обеспечивающих проверку типов. Но использование препроцессора и макросов также имеет свою сферу применения.
Например, функции отладки и трассировки. Согласитесь, что это довольно удобно:
#ifdef USE_MY_TRACE
#define TRACE(a) CallTrace(a)
#else
#define TRACE(a) ((void)0)
#endif

Если USE_MY_TRACE не определено, вызовы CallTrace просто будут исключены на уровне препроцессора, а оптимизация при компоновке просто не включит нигде теперь не используемую функцию CallTrace в конечный исполняемый код программы. Удобно, но...
Обратим внимание на семейство макросов TRACE0, TRACE1, TRACE2, TRACE3, объявленных в MFC. Невозможность обойтись одним универсальным макросом объясняется следующими правилами синтаксиса:

1. При объявлении нескольких макросов с одинаковым именем препроцессор использует последнее определение и выводит предупреждения на этапе трансляции.

2. Следует из первого - в отличие от функций, макросы с одинаковыми именами, но различным числом или типом параметров, недопустимы.

3. Синтаксическая запись произвольного числа параметров (многоточие, '...') для макроопределений недопустима и является синтаксической ошибкой.


Умные макроопределения.

Оказывается, преодолеть названные выше недостатки макросов совершенно несложно. Сделать это можно при помощи простого трюка - использования класса, чем-то напоминающего так называемую идиому функторов. Это класс, для которого определен набор операторов "скобки". Итак, например, вот такой класс:
class MacroCall
{
public:

MacroCall()
{
}

void operator()(float val) const
{
printf("Float: %f\r\n", val);
}

void operator()(int val) const
{
printf("Integer: %d\r\n", val);
}

void operator() (const char *pszFmt, ...) const
{
if ( pszFmt == NULL || *pszFmt == 0 )
return;

va_list args;
va_start(args, pszFmt);

int size_msgbuf = _vscprintf(pszFmt, args) + 1;
char* msgbuf = new char[size_msgbuf];
vsprintf(msgbuf, pszFmt, args);

printf(msgbuf);

delete[] msgbuf;
va_end(args);
}
};
А теперь объявим макроопределение:
#ifdef USE_MACRO
#define MYMACRO MacroCall()
#else
#define MYMACRO __noop
#endif
И, наконец, пример использования:
MYMACRO("%s : %d\r\n", "Value", 10);
MYMACRO(55);
MYMACRO(3.1415926f);



Краткое обьяснение.

Всё очень просто. Строка вызова макроопределения
MYMACRO(55);
заменяется препроцессором на вызов
MacroCall()(55);
Т.е. вызываются конструктор и соответствующий «оператор скобки». Можно записать так:
MacroCall().operator()(55);

Заметим, что вызывается тот «оператор скобки», который соответствует типу и количеству аргументов – соответственно, для типов float и int разные при внешне одинаковом вызове одного и того же макроса:
MYMACRO(55); // вызван operator()(int val)
MYMACRO(3.1415926f); // вызван operator()(float val)
// operator() (const char *pszFmt, ...)
MYMACRO("%s : %d\r\n", "Value", 10);



Дополнительные замечания.

1. Обратим внимание на то, что в случае, если макрос MYMACRO не используется, он заменяется на __noop, специально введенный в компиляторе от Microsoft. Согласно документации, он позволяет компилятору правильно «проигнорировать» ненужный теперь список аргументов вызова при произвольном числе аргументов.
Однако если компилятор не поддерживает __noop или нечто аналогичное, можно просто определять неиспользуемый макрос как «пустой» или как ((void)0):

#define MYMACRO
или
#define MYMACRO ((void)0)


2. Сам вызов конструктора тоже может быть использован для дополнительных аргументов. Например:
class MacroCallLine
{
public:

MacroCallLine(int L) : line_num(L)
{
}

void operator()(const char* msg) const
{
printf("Line: %d Message: %s\r\n", line_num, msg);
}

protected:
int line_num;
};

Теперь определим макрос:
#define TRACEMSG MacroCallLine(__LINE__)

Макрос теперь автоматически получает номер строки вызова.
И если его использовать где-нибудь в программе:
TRACEMSG("My message");
то мы получим что-то вроде следующего:
Line: 10 Message: My message

Замечу, что кроме __LINE__, определены также
__DATE__, __FILE__ и многое другое, и что особенно ценно на мой взгляд, __FUNCTION__. Замечу, что __FUNCTION__ работает удивительно корректно, возвращая имя класса и имя метода, разделенных '::'. Причем всё вышеназванное работает и для release версии, открывая прекрасные возможности для трассировки и доводки.


И напоследок.

1. Еще раз хочу обратить внимание: объявление макроса – это вызов конструктора, возможно с параметрами. Сам макрос не предполагает передачу аргументов. Например, был объявлен макрос в виде:
#define MYMACRO MacroCall()

Если объявить в следующей форме:
#define MYMACRO(p) MacroCall(p) // ЭТО УЖЕ ДРУГОЙ ВЫЗОВ

то это уже другая техника и другой случай, то есть это уже не вызов «оператора скобки», на котором всё и базируется.

2. В классе MacroCall показан пример использования произвольного числа аргументов и форматирования при помощи vsprintf. Подробно обьяснять этот фрагмент я не буду, поскольку эти функции подробно описаны в MSDN.

Категория: С/С++/MFC | Добавил: Cromartie (31.01.2013)
Просмотров: 492 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Наш опрос
Оцените мой сайт
Всего ответов: 543
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Реклама
Cheсking
Часы
Мини-чат
200
Друзья Сайта
  • Light Midnight - Ваша Еда
  • Light Midnight - Anim as life style
  • Поиск