// Помните, что итератор отображения ссылается на объект
// типа pair<Key,Val>. Указатель на набор хранится в p->second.
for (SetStr::iterator pSql = p->second->begin;
pSql != p->second->end; ++pSql) {
string s = *pSql;
conn_->execSql(s);
cout << "Executing SQL: " << s << endl;
}
conn_->endTxn;
delete p->second;
}
log_.clear;
}
void purge {
for (MapStrSetStr::iterator p = log_.begin;
p != log_.end; ++p)
delete p->second;
log_.clear;
}
//...
private:
MapStrSetStr log_;
DBConn* conn_;
}
;
Обсуждение
Пример 6.12
предлагает ситуацию, где может потребоваться хранение одного контейнера в другом. Представьте, что требуется сохранить набор выражений SQL в виде пакета, выполнить их в будущем все сразу для реляционной базы данных. Именно это делает
SimpleTxnLog
. Чтобы сделать его еще полезнее, можно добавить в него другие методы, а для обеспечения безопасности — добавить обработку исключений, но целью этого примера является показать, как хранить один тип контейнеров в другом.
При использовании шаблонов шаблонов (шаблонов… и т.д.) объявления становятся очень длинными, что затрудняет их чтение, так что облегчите себе жизнь, использовав
typedef
. Более того, использование
typedef
облегчает внесение изменений в объявление шаблонов, избавляя от необходимости выполнять поиск и замену во многих местах большого количества исходных файлов.
Класс
DBConn
— это фиктивный класс, который представляет подключение к реляционной базе данных. Интересно здесь то, как в
SimpleTxnLog
определяется метод
addTxn
. В начале этой функции я смотрю, существует ли уже объект набора для переданного
id
.
SetStr* pSet = log_[id];
log_
— это
map
(см. рецепт 6.6), так что
operator[]
выполняет поиск
id
и смотрит, связаны ли с ним какие-либо данные. Если да, то возвращается объект данных, и
pSet
не равен
NULL
. Если нет, он создается, и возвращается указатель, который будет равен
NULL
. Затем я проверяю, указывает ли на что-то
pSet
, и определяю, требуется ли создать еще один набор.
if (pSet == NULL) {
pSet = new SetStr; // SetStr = std::set<std::string>
log_[id] = pSet;
}
Так как
pSet
— это копия объекта данных, хранящихся в map (указатель на набор), а не само значение, то после создания
set
я должен поместить его обратно в связанный с ним ключ в
map
. После этого все, что остается сделать, — это добавить элемент в набор и выйти.
pSet->insert(sql);
Выполнив указанные шаги, я в один контейнер (
map
) добавил указатель на адрес другого контейнера (
set
). Что я не делал — это добавление объекта
set
в
map
. Разница очень существенна. Так как контейнеры обладают семантикой копирования, следующий код приведет к копированию всего набора
s
в
map
.
set<string> s;
// Заполнить s данными...
log_[id] = s; // Скопировать s и добавить его копию в log_
Это приведет к огромному числу дополнительных нежелательных копирований. Следовательно, общее правило при использовании контейнеров из контейнеров — это использовать указатели на контейнеры.
Глава 7
Алгоритмы
7.0. Введение
Эта глава рассказывает, как работать со стандартными алгоритмами и как использовать их для стандартных контейнеров. Эти алгоритмы первоначально являлись частью того, что часто называется Standard Template Library (STL — стандартная библиотека шаблонов) и представляет собой набор алгоритмов, итераторов и контейнеров, которые теперь вошли в стандартную библиотеку (глава 6 содержит рецепты по работе со стандартными контейнерами). Я их буду называть просто стандартными алгоритмами, итераторами и контейнерами, но не забывайте, что это то же самое, что другие авторы называют частью STL. Одним из базовых элементов стандартной библиотеки являются итераторы, так что первый рецепт описывает, что они собой представляют и как их использовать. После этого идет несколько рецептов, которые объясняют, как использовать и расширять стандартные алгоритмы. Наконец, если вы не нашли ничего подходящего в стандартной библиотеке, то рецепт 7.10 расскажет, как написать собственный алгоритм.
Представленные здесь рецепты в основном предназначены для работы со стандартными контейнерами, и тому есть две причины. Во-первых, стандартные контейнеры очень распространены, и лучше изучить стандарт, чем изобретать колесо. Во-вторых, реализация алгоритмов из стандартной библиотеки предоставляет хороший пример для подражания в смысле взаимодействия и производительности. Если вы посмотрите, как профессионалы выполнили код стандартной библиотеки, вы, скорее всего, узнаете много нового и полезного для себя.
Все стандартные алгоритмы используют итераторы. Даже если вы знакомы с концепцией итераторов, которые рассматриваются в первом рецепте, посмотрите на табл. 7.1, которая содержит перечень соглашений, используемых в остальных рецептах главы при демонстрации объявлений функций стандартных алгоритмов.