Рассмотрим устройство _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
Никакая часть данного материала не может быть использована в коммерческих целях без письменного разрешения автора.