Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.216.168.162] |
|
Сообщ.
#1
,
|
|
|
Макроопределения и их недостатки.
Использовать макроопределения иногда удобно. Во многих случаях, конечно, стоит предпочесть использование параметризованных функций (шаблонов) и других механизмов, обеспечивающих проверку типов. Но использование препроцессора и макросов также имеет свою сферу применения. Например, функции отладки и трассировки. Согласитесь, что это довольно удобно: #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. |
Сообщ.
#2
,
|
|
|
Цитата Mik @ Здесь надо бы подкорректировать. B С99 допустимо макросу иметь переменное количество параметров и ошибкой там это не является. Синтаксическая запись произвольного числа параметров (многоточие, '...') для макроопределений недопустима и является синтаксической ошибкой. Добавлено Цитата Mik @ А не простое переопределение ли при этом происходит? А не игнорирование. Надо проверить. При объявлении нескольких макросов с одинаковым именем препроцессор использует самое первое встреченное им объявление. Остальные вызывают вывод предупреждения на этапе трансляции и игнорируются |
Сообщ.
#3
,
|
|
|
Цитата trainer @ А не простое переопределение ли при этом происходит? А не игнорирование. Надо проверить. Страутруп по этому поводу говорит, что происходит замещение. |
Сообщ.
#4
,
|
|
|
Действительно
Цитата byte @ А не простое переопределение ли при этом происходит? Действительно происходит переопределение, я ошибся. Исправляю. Кстати, способ управления переопределением - это использование #pragma push_macro("macro") // ............. #pragma pop_macro("macro") |