Хочу представить или даже напомнить о таком замечательном модуле Boost как multi_index_container. Все мы знаем и интенсивно пользуем стандартные STL классы для хранения и доступа к данным как списки, векторы, мапы?, хеши. Про них сказано уже немало и исследованы все особенности их применений.
Всё усложняется когда возникает необходимость в классе для хранения и доступа к объектам по более чем одному ключу или их комбинациям. Обычно начинают с создания двух мапов или хешей, потом при увеличении их числа всё становится сложнее и возникают усложнённые участки кода для синхронизации этих хешей, вставок, удаления и переименований плюс становится довольно сложно понять стоимость той или иной операции. Ну и конечно создание таких велосипедов ведёт к большому количеству багов, необходимости оптимизаций и так далее.
Конечно всё уже придуманно до нас и в библиотеке Boost уже есть модуль для решения данных проблем – boost::multi_index. Огромное преимущество – скорость, multi_index очень быстрый. Однако документация этого модуля скажем сложна для понимания и новички стараются обходить этот модуль стороной. И конечно отдельно можно сказать о сообщениях компилятора при ошибках при работе с multi_index_container – разобрать длинные сообщение о шаблонах не каждому под силу.
Постараемся ликвидировать этот пробел и показать простые примеры для горячего старта и использования этого мощного инструмента. Буду пользовать немного вместе с Qt в примерах. (Как раз в Qt с их собственной системой шаблонов мне часто не хватает примитива сравнимого с multi_index)
Допустим у нас есть небольшая структурка с полями:
struct Rec { QString name, phone, addr; };
multi_index даёт очень удобную возможность использования тэгов/(метки в рус доке) для ключей.
Определим их прямо в Rec чтобы не расползались по коду:
Мы принимаем что записи у нас уникальны по имени(name), но неуникальны по адресу и телефону. Уникальность по имени также в том что мы не сможем добавить в массив две записи с одним и тем же именем.
Для меня это важное удобство что сам массив не допустит появления дубликатов и при доступе к нему я могу полагаться на правильность содержимого a priori.
Найдём запись по имени:
{
QString find_id = "Basilio Pupkinio";
typedef Store::index<Rec::ByName>::type List;
const List & ls = store.get<Rec::ByName>();
List::const_iterator it = ls.find(find_id);
if ( it != ls.end() ) {
qDebug() << (*it).addr; // "Neron st"
}
}
Опять же понимаем что для композитных ключей мы можем пользовать как и ordered_non_unique так и ordered_unique.
Таим способом очень удобно накладывать некоторые дополнительные условия на содержимое дозволенных ключей и их комбинаций - сам масив не даст нам добавить "неправильные" объекты.
Если мы хотим возможность иметь доступ в массив одновременно как к вектору мы можем легко добавить random_access:
Тогда у нас есть возможность доступа как store[0] итд, push_back().
Если мы хотим использовать массив как хеш чтобы иметь быстрый доступ по ключу О(1), но медленнее O(log(n)) по insert/remove то мы можем пользовать hashed_unique/hashed_non_unique вместо ordered_unique/ordered_non_uniue:
Также можно использовать hashed_uniqie/hashed_non_unique и для композитных ключей.
О модификации записей.
Так как поля объектов должны быть синхронизированными с индексами массива нельзя просто изменить только поле у объекта. Допустим у нас есть необходимость изменить телефон для одной из записей. Чтобы наши ключи были синхронизированными с объектами это изменение должно быть сделано через функтор:
Мы можем сделать поиск в масиве по имени, получить итератор по имени, с помощью project() перевести его в итератор по телефону и провести модификацию объекта и массива через функтор:
Как результат можно построить очень эффективные по доступу структуры данных связанными между собой например умными указателями где для каждой операции довольно легко предсказать затраты на доступ к тем или иным данным. В каком-то смысле multi_index_container можно рассматривать как очень быструю таблицу базы данных в памяти для доступа по заранее заданным ключам.
Надеюсь простыми примерами я даю возможность "горячего старта" пользования этого замечательного инструмента, так как изучениe оригинальной документации и попытки разобрать ошибки компилятора о шаблонах в использовании multi_index вызывают сразу большое уныние у новичков и попытки сделать что-то своё. Получается как всегда.
Ru: Archive: O Boost Multi-index Containers
Хочу представить или даже напомнить о таком замечательном модуле Boost как multi_index_container. Все мы знаем и интенсивно пользуем стандартные STL классы для хранения и доступа к данным как списки, векторы, мапы?, хеши. Про них сказано уже немало и исследованы все особенности их применений.
Всё усложняется когда возникает необходимость в классе для хранения и доступа к объектам по более чем одному ключу или их комбинациям. Обычно начинают с создания двух мапов или хешей, потом при увеличении их числа всё становится сложнее и возникают усложнённые участки кода для синхронизации этих хешей, вставок, удаления и переименований плюс становится довольно сложно понять стоимость той или иной операции. Ну и конечно создание таких велосипедов ведёт к большому количеству багов, необходимости оптимизаций и так далее.
Конечно всё уже придуманно до нас и в библиотеке Boost уже есть модуль для решения данных проблем – boost::multi_index. Огромное преимущество – скорость, multi_index очень быстрый. Однако документация этого модуля скажем сложна для понимания и новички стараются обходить этот модуль стороной. И конечно отдельно можно сказать о сообщениях компилятора при ошибках при работе с multi_index_container – разобрать длинные сообщение о шаблонах не каждому под силу.
Постараемся ликвидировать этот пробел и показать простые примеры для горячего старта и использования этого мощного инструмента. Буду пользовать немного вместе с Qt в примерах. (Как раз в Qt с их собственной системой шаблонов мне часто не хватает примитива сравнимого с multi_index)
Допустим у нас есть небольшая структурка с полями:
multi_index даёт очень удобную возможность использования тэгов/(метки в рус доке) для ключей.
Определим их прямо в Rec чтобы не расползались по коду:
Мы можем создать следующий массив:
Мы принимаем что записи у нас уникальны по имени(name), но неуникальны по адресу и телефону. Уникальность по имени также в том что мы не сможем добавить в массив две записи с одним и тем же именем.
Для меня это важное удобство что сам массив не допустит появления дубликатов и при доступе к нему я могу полагаться на правильность содержимого a priori.
Найдём запись по имени:
Если у нас несколько записей с телефоном 022
Тогда найти все записи с телефоном 022
Что если нам интересен поиск по комбинации телефона и адреса?
Мы можем добавить композитный индекс в наш массив таким блоком:
(Плюс не забываем добавить метку struct ByKey {}; )
Опять же понимаем что для композитных ключей мы можем пользовать как и ordered_non_unique так и ordered_unique.
Таим способом очень удобно накладывать некоторые дополнительные условия на содержимое дозволенных ключей и их комбинаций - сам масив не даст нам добавить "неправильные" объекты.
Если мы хотим возможность иметь доступ в массив одновременно как к вектору мы можем легко добавить random_access:
Тогда у нас есть возможность доступа как store[0] итд, push_back().
Если мы хотим использовать массив как хеш чтобы иметь быстрый доступ по ключу О(1), но медленнее O(log(n)) по insert/remove то мы можем пользовать hashed_unique/hashed_non_unique вместо ordered_unique/ordered_non_uniue:
Также можно использовать hashed_uniqie/hashed_non_unique и для композитных ключей.
О модификации записей.
Так как поля объектов должны быть синхронизированными с индексами массива нельзя просто изменить только поле у объекта. Допустим у нас есть необходимость изменить телефон для одной из записей. Чтобы наши ключи были синхронизированными с объектами это изменение должно быть сделано через функтор:
Мы можем сделать поиск в масиве по имени, получить итератор по имени, с помощью project() перевести его в итератор по телефону и провести модификацию объекта и массива через функтор:
Использование индексов не ограничено полями - можно пользовать и методы классов, например:
Отдельно стоит упомянуть использование указателей:
Вся остальная логика остаётся практически той же самой (только -> вместо . в пару мест).
Плюс стоит добавлять например блок
Чтобы исключать добавление дупликатов указателей.
Как результат можно построить очень эффективные по доступу структуры данных связанными между собой например умными указателями где для каждой операции довольно легко предсказать затраты на доступ к тем или иным данным. В каком-то смысле multi_index_container можно рассматривать как очень быструю таблицу базы данных в памяти для доступа по заранее заданным ключам.
Надеюсь простыми примерами я даю возможность "горячего старта" пользования этого замечательного инструмента, так как изучениe оригинальной документации и попытки разобрать ошибки компилятора о шаблонах в использовании multi_index вызывают сразу большое уныние у новичков и попытки сделать что-то своё. Получается как всегда.