Чтение онлайн

ЖАНРЫ

C++. Сборник рецептов

Когсуэлл Джефф

Шрифт:

set
поддерживает многие из функций, поддерживаемых другими стандартными последовательными контейнерами (например,
begin
,
end
,
size
,
max_size
) и другими ассоциативными контейнерами (например,
insert
,
erase
,
clear
,
find
).

При использовании

set
помните, что при каждом изменении состояния набора выполняется его сортировка. Когда число его элементов велико, логарифмическая сложность добавления или удаления элементов может оказаться очень большой — вам действительно требуется, чтобы объекты
сортировались каждый раз? Если нет, то для повышения производительности используйте
vector
или
list
и сортируйте его только тогда, когда это необходимо, что обычно имеет сложность порядка n*log(n).

6.9. Хранение контейнеров в контейнерах

Проблема

Имеется несколько экземпляров стандартного контейнера (

list
,
set
и т.п.) и требуется сохранить их в еще одном контейнере.

Решение

Сохраните в главном контейнере указатели на остальные контейнеры. Например, можно использовать

map
для хранения ключа типа
string
и указателя на
set
как значения. Пример 6.12 показывает простой класс журналирования транзакций, который хранит данные как map из пар, состоящих из
string
и указателей на
set
.

Пример 6.12. Хранение набора указателей в отображении

#include <iostream>

#include <set>

#include <map>

#include <string>

using namespace std;

typedef set<string> SetStr

typedef map<string, SetStr*> MapStrSetStr;

// Фиктивный класс базы данных

class DBConn {

public:

 void beginTxn {}

 void endTxn {}

 void execSql(string& sql) {}

};

class SimpleTxnLog {

public:

 SimpleTxrLog {}

 ~SimpleTxrLog {purge;}

 // Добавляем в список выражение SQL

 void addTxn(const string& id

const string& sql) {

SetStr* pSet = log_[id]; // Здесь создается запись для

if (pSet == NULL) { // данного id, если ее еще нет

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);

Поделиться с друзьями: