C++ 3d.Комментарии

       

Макросы


В C++ макросы не нужны! До боли знакомое высказывание, не так ли? Я бы его немного уточнил: не нужны, если вы не хотите существенно облегчить себе жизнь.

Я полностью согласен с тем, что чрезмерное и необдуманное использование макросов может вызвать большие неприятности, особенно при повторном использовании кода. Вместе с тем, я не знаю ни одного средства C++, которое могло бы принести пользу при чрезмерном и необдуманном его использовании.

Итак, когда макросы могут принести пользу?

    Макрос как надъязыковое средство. Хороший примером является простой, но удивительно полезный отладочный макрос _VAL_, выводящий имя и значение переменной: #define _VAL_(var) #var "=" << var << " "

    Надъязыковой частью здесь является работа с переменной как с текстом, путем перевода имени переменной (оно существует только в исходном коде программы) в строковый литерал, реально существующий в коде бинарном. Данную возможность могут предоставить только макросы.

Информация о текущем исходном файле и строке -- ее пользу при отладке трудно переоценить. Для этого я использую специальный макрос _ADD_. Например: cout<<_ADD_("Ошибка чтения");

выведет что-то вроде Ошибка чтения <file.cpp:34>

А если нужен перевод строки, то стоит попробовать cout<<"Ошибка чтения" _ADD_("") "\n";

Такой метод работает, потому что макрос _ADD_ возвращает строковый литерал. Вроде бы эквивалентная функция char* _ADD_(char*);

вполне подошла бы для первого примера, но не для второго. Конечно, для вывода в cout это не имеет никакого значения, но в следующем пункте я покажу принципиальную важность подобного поведения.

Рассмотрим устройство _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

Никакая часть данного материала не может быть использована в коммерческих целях без письменного разрешения автора. Обращений с начала месяца: 197, Last-modified: Tue, 12 Oct 2004 18:11:49 GMT Оцените этот текст:Не читал10987654321


Содержание раздела