pSet = new SetStr();
log_[id] = pSet;
}
pSet->insert(sol);
}
// Применение выражений SQL к базе данных, по одной транзакции
// за один раз
void apply() {
for (MapStrSetStr::iterator p = log_.begin();
p != log_.end(); ++p) {
conn_->beginTxn();
// Помните, что итератор отображения ссылается на объект
// типа 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 std::set<std::string> SetStr;
typedef std::map<std::string, SetStr*> MapStrSetStr;
При использовании шаблонов шаблонов (шаблонов… и т.д.) объявления становятся очень длинными, что затрудняет их чтение, так что облегчите себе жизнь, использовав 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, которая содержит перечень соглашений, используемых в остальных рецептах главы при демонстрации объявлений функций стандартных алгоритмов.
Табл. 7.1. Сокращения категорий итераторов
| Сокращение | Значение |
|---|---|
| In | Input iterator (Итератор ввода) |
| Out | Output iterator (Итератор вывода) |
| Fwd | Forward iterator (Однонаправленный итератор) |
| Bid | Bidirectional iterator (Двунаправленный итератор) |
| Rand | Random-access iterator (Итератор произвольного доступа) |
Стандартные алгоритмы также используют функциональные объекты, или функторы. Функциональный объект — это класс, который переопределяет operator() так, что его можно вызвать как функцию. Функтор, который возвращает bool (и не поддерживает состояния, и, следовательно, называется чистым (pure), называется предикатом (predicate), и он является еще одной обычной функциональной особенностью стандартных алгоритмов. Обычно предикат принимает один или два аргумента: если он принимает один аргумент, то это унарный предикат, а если два — то бинарный предикат. Для краткости я при демонстрации объявлений функций использую сокращения, приведенные в табл. 7.2.