Рассмотрим устройство _ADD_:

#define _ADD_tmp_tmp_(str,arg) str " <" __FILE__ ":" #arg ">"
#define _ADD_tmp_(str,arg) _ADD_tmp_tmp_(str,arg)
#define _ADD_(str) _ADD_tmp_(str,__LINE__)

Почему все так сложно? Дело в том, что __LINE__ в отличие от __FILE__ является числовым, а не строковым литералом и чтобы привести его к нужному типу придется проявить некоторую смекалку. Мы, конечно, не можем написать:
#define _ADD_(str) str " <" __FILE__ ":" #__LINE__ ">"

т.к. # может быть применен только к аргументу макроса. Решением является передача __LINE__ в виде параметра некоторому вспомогательному макросу, но очевидное
#define _ADD_tmp_(str,arg) str " <" __FILE__ ":" #arg ">"
#define _ADD_(str) _ADD_tmp_(str,__LINE__)

не работает: результатом _ADD_("Ошибка чтения") будет
"Ошибка чтения <file.cpp:__LINE__>"

что нетрудно было предвидеть. В итоге мы приходим к приведенному выше варианту, который обрабатывается препроцессором следующим образом: _ADD_("Ошибка чтения") последовательно подставляется в
_ADD_tmp_("Ошибка чтения",__LINE__)
_ADD_tmp_tmp_("Ошибка чтения",34)
"Ошибка чтения" " <" "file.cpp" ":" "34" ">"
"Ошибка чтения <file.cpp:34>"




  • Получение значения числового макроса в виде строки. Как показывает практика, данная возможность находит себе применение и за пределами подробностей реализации "многоэтажных" макросов. Допустим, что для взаимодействия с SQL-сервером у нас определен класс DB::Query с соответствующей функцией
    void DB::Query::Statement(const char *);

    и мы хотим выбрать все строки некоторой таблицы, имеющие равное некому "магическому числу" поле somefield:
    #define FieldOK 7
    // ...
    DB::Int tmp(FieldOK);
    q.Statement(" SELECT * "
    " FROM sometable "
    " WHERE somefield=? "
    );
    q.SetParam(), tmp;

    Излишне многословно. Как бы это нам использовать FieldOK напрямую? Недостаточно знакомые с возможностями макросов программисты делают это так:
    #define FieldOK 7
    // ...
    #define FieldOK_CHAR "7"
    // ...
    q.Statement(" SELECT * "
    " FROM sometable "
    " WHERE somefield=" FieldOK_CHAR
    );

    В результате чего вы получаете все прелести синхронизации изменений взаимосвязанных наборов макросов со всеми вытекающими из этого ошибками. Правильным решением будет
    #define FieldOK 7
    // ...
    q.Statement(" SELECT * "
    " FROM sometable "
    " WHERE somefield=" _GETSTR_(FieldOK)
    );

    где _GETSTR_ определен следующим образом:
    #define _GETSTR_(arg) #arg

    Кстати, приведенный пример наглядно демонстрирует невозможность полностью эквивалентной замены всех числовых макросов на принятые в C++
    const int FieldOK=7;
    enum { FieldOK=7 };

    макрос _GETSTR_ не сможет с ними работать.



  • Многократно встречающиеся части кода. Рассмотрим еще один пример из области работы с SQL-сервером. Предположим, что нам нужно выбрать данные из некоторой таблицы. Это можно сделать в лоб:
    struct Table1 {  // представление данных таблицы
    DB::Date Field1;
    DB::Int Field2;
    DB::Short Field3;
    };

    void f()
    {
    Table1 tbl;
    DB::Query q;
    q.Statement(" SELECT Field1, Field2, Field3 "
    " FROM Table1 "
    );
    q.BindCol(), tbl.Field1, tbl.Field2, tbl.Field3;
    // ...
    }

    И этот метод действительно работает. Но что, если представление таблицы изменилось? Теперь нам придется искать и исправлять все подобные места -- чрезвычайно утомительный процесс! Об этом стоило позаботиться заранее:
    #define TABLE1_FLD      Field1, Field2, Field3
    #define TABLE1_FLD_CHAR "Field1, Field2, Field3"

    struct Table1 { // представление данных таблицы
    DB::Date Field1;
    DB::Int Field2;
    DB::Short Field3;

    // вспомогательная функция
    void BindCol(DB::Query& q) { q.BindCol(), TABLE1_FLD; }
    };

    void f()
    {
    Table1 tbl;
    DB::Query q;
    q.Statement(" SELECT " TABLE1_FLD_CHAR
    " FROM Table1 "
    );
    tbl.BindCol(q);
    // ...
    }

    Теперь изменение структуры таблицы обойдется без зубовного скрежета. Стоит отметить, что в определении TABLE1_FLD_CHAR я не мог использовать очевидное _GETSTR_(TABLE1_FLD), т.к. TABLE1_FLD содержит запятые. К сожалению, данное печальное ограничение в примитивном препроцессоре C++ никак нельзя обойти.



  • Многократно встречающиеся подобные части кода. Представим себе, что мы пишем приложение для банковской сферы и должны выбрать информацию по некоторым счетам. В России, например, счет состоит из многих полей, которые для удобства работы собирают в специальную структуру, а в таблице он может быть представлен смежными полями с одинаковым префиксом:
    q.Statement(" SELECT Field1, AccA_bal, AccA_cur, AccA_key, AccA_brn, "
    " AccA_per, Field2 "
    " FROM Table1 "
    );
    q.BindCol(), tbl.Field1, tbl.AccA.bal, tbl.AccA.cur, tbl.AccA.key,
    tbl.AccA.brn, tbl.AccA.per, tbl.Field2;
    // ...

    Можете себе представить, сколько писанины требуется для выбора четырех счетов (tbl.AccA, tbl.AccB, tbl.KorA, tbl.KorB). И снова на помощь приходят макросы:
    #define _SACC_(arg) #arg"_bal, "#arg"_cur, "#arg"_key, "#arg"_brn, " \
    #arg"_per "
    #define _BACC_(arg) arg.bal, arg.cur, arg.key, arg.brn, arg.per

    // ...

    q.Statement(" SELECT Field1, " _SACC_(AccA) " , Field2 "
    " FROM Table1 "
    );
    q.BindCol(), tbl.Field1, _BACC_(tbl.AccA), tbl.Field2;
    // ...

    Думаю, что комментарии излишни.



  • Рассмотрим более тонкий пример подобия. Пусть нам потребовалось создать таблицу для хранения часто используемой нами структуры данных:
    struct A {
    MyDate Date;
    int Field2;
    short Field3;
    };

    Мы не можем использовать идентификатор Date для имени столбца таблицы, т.к. DATE является зарезервированным словом SQL. Эта проблема легко обходится с помощью приписывания некоторого префикса:
    struct TableA {
    DB::Date xDate;
    DB::Int xField2;
    DB::Short xField3;

    TableA& operator=(A&);
    void Clear();
    };

    А теперь определим функции-члены:
    TableA& TableA::operator=(A& a)
    {
    xDate=ToDB(a.Date);
    xField2=ToDB(a.Field2);
    xField3=ToDB(a.Field3);

    return *this;
    }

    void TableA::Clear()
    {
    xDate="";
    xField2="";
    xField3="";
    }

    Гарантирую, что если TableA содержит хотя бы пару-тройку десятков полей, то написание подобного кода вам очень быстро наскучит, мягко говоря! Нельзя ли это сделать один раз, а потом использовать результаты? Оказывается можно:
    TableA& TableA::operator=(A& a)
    {
    // используем склейку лексем: ##
    #define ASS(arg) x##arg=ToDB(a.arg);
    ASS(Date);
    ASS(Field2);
    ASS(Field3);
    #undef ASS

    return *this;
    }

    void TableA::Clear()
    {
    #define CLR(arg) x##arg=""
    CLR(Date);
    CLR(Field2);
    CLR(Field3);
    #undef CLR
    }

    Теперь определение TableA::Clear()по TableA::operator=() не несет никакой нудной работы, если, конечно, ваш текстовый редактор поддерживает команды поиска и замены. Так же просто можно определить и обратное присваивание: A& A::operator=(TableA&).


  • Надеюсь, что после приведенных выше примеров вы по-новому посмотрите на роль макросов в C++.




    Copyright © С. Деревяго, 2000-2004


    Никакая часть данного материала не может быть использована в коммерческих целях без письменного разрешения автора.