На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
! Правила раздела FAQ в группе разделов С++.
1. Раздел FAQ предназначен для публикации готовых статей.
2. Здесь нельзя задавать вопросы, для этого существуют соответствующие разделы:
Чистый С++
Visual C++ / MFC / WTL / WinApi
Borland C++ Builder
COM / DCOM / ActiveX / ATL
Сопутствующие вопросы
3. Внимание, все темы и сообщения в разделе премодерируются. Любое сообщение или тема будут видны остальным участникам только после одобрения модератора.
Модераторы: B.V., Qraizer
  
> Макрос с произвольными параметрами, Пример техники работы с макросами
    Макроопределения и их недостатки.

    Использовать макроопределения иногда удобно. Во многих случаях, конечно, стоит предпочесть использование параметризованных функций (шаблонов) и других механизмов, обеспечивающих проверку типов. Но использование препроцессора и макросов также имеет свою сферу применения.
    Например, функции отладки и трассировки. Согласитесь, что это довольно удобно:
    ExpandedWrap disabled
      #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. Синтаксическая запись произвольного числа параметров (многоточие, '...') для макроопределений недопустима и является синтаксической ошибкой.


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

    Оказывается, преодолеть названные выше недостатки макросов совершенно несложно. Сделать это можно при помощи простого трюка - использования класса, чем-то напоминающего так называемую идиому функторов. Это класс, для которого определен набор операторов "скобки". Итак, например, вот такой класс:
    ExpandedWrap disabled
      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);
          }
      };
    А теперь объявим макроопределение:
    ExpandedWrap disabled
      #ifdef USE_MACRO
      #define MYMACRO MacroCall()
      #else
      #define MYMACRO __noop
      #endif
    И, наконец, пример использования:
    ExpandedWrap disabled
      MYMACRO("%s : %d\r\n", "Value", 10);
      MYMACRO(55);
      MYMACRO(3.1415926f);



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

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

    Заметим, что вызывается тот «оператор скобки», который соответствует типу и количеству аргументов – соответственно, для типов float и int разные при внешне одинаковом вызове одного и того же макроса:
    ExpandedWrap disabled
      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):

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


    2. Сам вызов конструктора тоже может быть использован для дополнительных аргументов. Например:
    ExpandedWrap disabled
      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;
      };

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

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

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


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

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

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

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

    2. В классе MacroCall показан пример использования произвольного числа аргументов и форматирования при помощи vsprintf. Подробно обьяснять этот фрагмент я не буду, поскольку эти функции подробно описаны в MSDN.
    Сообщение отредактировано: Mik -
    Как жаль, что в нашей жизни нет операции «Undo»…
      Цитата Mik @
      Синтаксическая запись произвольного числа параметров (многоточие, '...') для макроопределений недопустима и является синтаксической ошибкой.
      Здесь надо бы подкорректировать. B С99 допустимо макросу иметь переменное количество параметров и ошибкой там это не является.

      Добавлено
      Цитата Mik @
      При объявлении нескольких макросов с одинаковым именем препроцессор использует самое первое встреченное им объявление. Остальные вызывают вывод предупреждения на этапе трансляции и игнорируются
      А не простое переопределение ли при этом происходит? А не игнорирование. Надо проверить. :)
      Во имя Ctrl, Alt и святаго Del, Enter!

      Основам программирования не обучаю. Не интересно.
        Цитата trainer @
        А не простое переопределение ли при этом происходит? А не игнорирование. Надо проверить.

        Страутруп по этому поводу говорит, что происходит замещение.
          Действительно
          Цитата byte @
          А не простое переопределение ли при этом происходит?


          Действительно происходит переопределение, я ошибся. Исправляю.
          Кстати, способ управления переопределением - это использование

          #pragma push_macro("macro")
          // .............
          #pragma pop_macro("macro")
          Как жаль, что в нашей жизни нет операции «Undo»…
          1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
          0 пользователей:


          Рейтинг@Mail.ru
          [ Script Execution time: 0,1087 ]   [ 14 queries used ]   [ Generated: 20.08.19, 13:59 GMT ]