Использование библиотеки STL
Проблемы с компиляцией программ под STL
и их красивое решение в GNU C++
Следует сделать важное замечание о компиляции программ в STL. Дело в том, что STL распространяется в исходных кодах (это существенно влияет на производительность результирующего кода), поэтому зачастую строка с ошибкой будет указывать не на ваш код, а на внутренности STL. При этом строка с описанием ошибки будет занимать несколько сотен символов, что не сразу проливает свет на истинную причину её возникновения. Разберём только один простой пример.
Пусть мы передаём vector<int> как const reference параметр (как это и следует делать) в некую функцию. А в этой функции мы работаем с этим массивом посредством итераторов:
void f(const vector<int>& v) {
for(
vector<int>::iterator it = v.begin();
// ну что же здесь не так
...
...
}
В этом коротком участке кода есть ошибка. Вы можете её обнаружить?
Дело в том, что из немодифицируемого (const) объекта мы пытаемся получить модифицируемый (non-const) итератор при помощи функции begin(). Подобное преобразование является недопустимым. Корректный код выглядит следующим образом:
void f(const vector<int>& v) {
int r = 0;
// используется const_iterator
for(vector<int>::const_iterator it = v.begin();
it != v.end(); it++) {
r += (*it) * (*it);
}
return r;
}
В свете всего вышесказанного, имеет смысл знать про одну очень интересную функцию компилятора GNU C++. Заметим, что данная функция не входит в cтандарт C++, поэтому в большинстве других компиляторов её нет.
Итак, «оператор» typeof(x). На этапе компиляции он заменяется на тип выражения в скобках. Пример:
typeof(a+b) x = (a+b);Переменная x будет иметь тип, соответствующий типу выражения a+b. Напомним, что typeof(c.size()) -- unsigned int для всех контейнеров STL. Но наибольшую пользу можно извлечь из typeof в следующем контексте:
#define tr(container, iterator) \ for(typeof(container.begin()) iterator=container.begin(); \ it != container.end(); it++)Данный макрос -- сокращение от traverse -- будет работать даже для самого сложного типа контейнера, независимо от того, каким образом этот контейнер к моменту использования определён. Для const-объектов он породит const_iterator'ы:
void f(const vector<int>& v) {
int r = 0;
tr(v, it) {
r += (*it)*(*it);
}
return r;
}
Подобные ухищрения не столь важны при работе с векторами, но они совершенно незаменимы при работа с более сложными контейнерами. Это чувствуется особенно хорошо, когда код принимает примерно следующий вид:
void operate(const map< set< pair<int,int> >,
vector< pair< double, pair<int,int> > > > &object) {
for( ... ) // MAMMA MIA!
}
Хотя, например, такая операция, как подсчитать сумму всех double из правой части map с использованием макроса tr выполняется сравнительно просто:
void operate(const map< set< pair<int,int> >,
vector< pair< double, pair<int,int> > > > &c) {
double result = 0;
tr(c, it) {
tr(it->second, it2) {
result += it2->first;
}
}
return result;
}