Навязчивыеассоциативные контейнеры предлагают интерфейс, аналогичный ассоциативным контейнерам STL. Однако упорядоченные и неупорядоченные простые ассоциативные контейнеры STL (<std::set>,<std::multiset>,<std::unordered_set>и<std::unordered_multiset>) имеют некоторые недостатки, вызванные интерфейсом в нескольких функциях поиска, вставки или стирания (<equal_range>,<lower_bound>,<upper_bound>,<find>,<insert>,<erase>...): пользователь может работать только с<value_type>объектами или (начиная с C++11),гетерогенными поисками сравнения, которые недостаточно гибки, поскольку<key_compare>поддерживает сравнение между предоставленным ключом и<value_type>, что исключает использование определяемых пользователем объектов сравнения, которые могут разделять поиск совместимым, но продвинутым способом.
Для решения этих задачBoost.Intrusiveконтейнеры предлагают функции, в которых пользователь предоставляет ключевой тип, отличный от<key_type>, и объект сравнения. Это относится к: * equal_range * lower_bound * upper_bound * count * find * erase
Требованиями к таким функциям являются:
Для неупорядоченного контейнера предусмотренная функция сравнения и хеширования с заданным ключом должна вызывать такой же хеш и эквивалентность, как<key_compare>и<hasher>.
Для заказанных ассоциативных контейнеров, функций поиска и стирания контейнер, подлежащий поиску, должен быть разделен в отношении поставляемого объекта сравнения и ключа.
Подробнее см.Требуетоговорок о таких функциях в ссылке.
Представьте себе, что объект, который нужно искать, довольно дорого построить (на примере<Expensive>):
#include<boost/intrusive/set.hpp>#include<boost/intrusive/unordered_set.hpp>#include<cstring>usingnamespaceboost::intrusive;// Hash function for stringsstructStrHasher{std::size_toperator()(constchar*str)const{std::size_tseed=0;for(;*str;++str)boost::hash_combine(seed,*str);returnseed;}};classExpensive:publicset_base_hook<>,publicunordered_set_base_hook<>{std::stringkey_;// Other members...public:Expensive(constchar*key):key_(key){}//other expensive initializations...conststd::string&get_key()const{returnkey_;}friendbooloperator<(constExpensive&a,constExpensive&b){returna.key_<b.key_;}friendbooloperator==(constExpensive&a,constExpensive&b){returna.key_==b.key_;}friendstd::size_thash_value(constExpensive&object){returnStrHasher()(object.get_key().c_str());}};// A set and unordered_set that store Expensive objectstypedefset<Expensive>Set;typedefunordered_set<Expensive>UnorderedSet;// Search functionsExpensive*get_from_set(constchar*key,Set&set_object){Set::iteratorit=set_object.find(Expensive(key));if(it==set_object.end())return0;return&*it;}Expensive*get_from_uset(constchar*key,UnorderedSet&uset_object){UnorderedSet::iteratorit=uset_object.find(Expensive(key));if(it==uset_object.end())return0;return&*it;}
Если «ключевая» c-струна довольно длинная<Expensive>, она должна построить<std::string>с использованием кучной памяти. Как и<Expensive>, во многих случаях единственным членом, принимающим участие в упорядочении вопросов, является лишь небольшая часть класса. Например, для сравнения объекта с<Expensive>требуется только внутреннее<std::string>.
В обоих контейнерах, если мы вызовем<get_from_set/get_from_unordered_set>по петле, мы можем получить штраф за производительность, потому что мы вынуждены создать целый<Expensive>объект, чтобы иметь возможность найти эквивалентный.
Иногда проблема связана не только с производительностью, поскольку у насможет не хватить информации для построения объекта, но у нас может бытьдостаточно информации, чтобы найти объект. В этом случае достаточно имени для поиска<Expensive>объектов в контейнере, но для создания<Expensive>объекта может потребоваться дополнительная информация, которой у искателя может не быть.
Чтобы решить эту проблему, мы можем использовать функции, которые принимают любой тип, сопоставимый со значением и «совместимым» объектом сравнения (и хэш, когда ассоциативный контейнер неупорядочен). Рассмотрим оптимизированную функцию поиска:
// These compare Expensive and a c-stringstructStrExpComp{booloperator()(constchar*str,constExpensive&c)const{returnstd::strcmp(str,c.get_key().c_str())<0;}booloperator()(constExpensive&c,constchar*str)const{returnstd::strcmp(c.get_key().c_str(),str)<0;}};structStrExpEqual{booloperator()(constchar*str,constExpensive&c)const{returnstd::strcmp(str,c.get_key().c_str())==0;}booloperator()(constExpensive&c,constchar*str)const{returnstd::strcmp(c.get_key().c_str(),str)==0;}};// Optimized search functionsExpensive*get_from_set_optimized(constchar*key,Set&set_object){Set::iteratorit=set_object.find(key,StrExpComp());if(it==set_object.end())return0;return&*it;}Expensive*get_from_uset_optimized(constchar*key,UnorderedSet&uset_object){UnorderedSet::iteratorit=uset_object.find(key,StrHasher(),StrExpEqual());if(it==uset_object.end())return0;return&*it;}
Аналогичная проблема возникает с вставками в простые упорядоченные и неупорядоченные ассоциативные контейнеры с уникальными ключами<std::set>и<std::unordered_set>. В этих контейнерах, если значение уже присутствует, вставленное значение отбрасывается. С дорогими значениями, если стоимость уже присутствует, мы можем столкнуться с проблемами эффективности.
<set>и<unordered_set>-подобные контейнеры имеют функции вставки<insert_check>,<insert_unique_check>, ...) для эффективной проверки, без построения значения, если значение присутствует или нет, и если оно отсутствует, функция для его немедленной вставки (54) без какого-либо дальнейшего поиска. Требования к функциям, которые проверяют наличие такого значения:
Для неупорядоченного контейнера предусмотренная функция сравнения и хеширования с заданным ключом должна вызывать такой же хеш и эквивалентность, как<key_compare>и<hasher>.
Для упорядоченных ассоциативных контейнеров предусмотренная функция сравнения с заданным ключом должна вызывать такой же строгий слабый порядок, как<key_compare>.
В общем,<insert_check>похоже на нормальное<insert>, но:
<insert_check>Может использоваться с произвольными ключами
Если вставка возможна (нет эквивалентного значения)<insert_check>собирает всю необходимую информацию в<insert_commit_data>структуре, так что<insert_commit>:
не выполняетдальнейших сравнений
может быть выполнена ссложностью в постоянное время
Эти функции должны использоваться с осторожностью, никакая другая вставка или стирание не должны выполняться между<insert_check>и<insert_commit>парой. В противном случае поведение не определено.
См.<set>и<unordered_set>-подобные ссылки контейнеров для получения дополнительной информации о<insert_check>и<insert_commit>.
При множественных упорядоченных и неупорядоченных ассоциативных контейнерах<multiset>и<unordered_multiset>нет необходимости в этих расширенных функциях вставки, поскольку вставки всегда успешны.
Если объект уже присутствует, мы строим<Expensive>, который будет выброшен, и это пустая трата ресурсов. Вместо этого воспользуемся функциями<insert_check>и<insert_commit>:
Некоторые упорядоченные ассоциативные контейнеры предлагают низкоуровневые функции для обхода проверок заказа и вставки узлов непосредственно в нужные позиции дерева. Эти функции предоставляются по причинам производительности, когда известно, что значения, которые должны быть вставлены в контейнер, выполняют порядок (наборы и мультинаборы) и инварианты уникальности (наборы). Типичное использование этих функций — когда для построения неинтрузивных контейнеров используются интрузивные ассоциативные контейнеры и программист хочет ускорить назначения из других ассоциативных контейнеров: если свойства упорядочивания и уникальности одинаковы, нет необходимости тратить время на проверку положения каждого исходного значения, потому что значения уже упорядочены: обратные вставки будут намного эффективнее.
Примечание:Эти функциине проверяют предварительные условия, поэтому они должны использоваться с осторожностью. Это низкоуровневые операции, которыеразбивают инварианты контейнера, если предварительные условия заказа и уникальности не обеспечены абонентом.
Давайте посмотрим пример:
#include<boost/intrusive/set.hpp>#include<vector>#include<functional>#include<cassert>usingnamespaceboost::intrusive;//A simple class with a set hookclassMyClass:publicset_base_hook<>{public:intint_;MyClass(inti):int_(i){}friendbooloperator<(constMyClass&a,constMyClass&b){returna.int_<b.int_;}friendbooloperator>(constMyClass&a,constMyClass&b){returna.int_>b.int_;}};intmain(){//Create some ORDERED elementsstd::vector<MyClass>values;for(inti=0;i<100;++i)values.push_back(MyClass(i));{//Data is naturally ordered in the vector with the same criteria//as multiset's comparison predicate, so we can just push back//all elements, which is more efficient than normal insertionmultiset<MyClass>mset;for(inti=0;i<100;++i)mset.push_back(values[i]);//Now check orderd invariantmultiset<MyClass>::const_iteratornext(mset.cbegin()),it(next++);for(inti=0;i<99;++i,++it,++next)assert(*it<*next);}{//Now the correct order for the set is the reverse order//so let's push front all elementsmultiset<MyClass,compare<std::greater<MyClass>>>mset;for(inti=0;i<100;++i)mset.push_front(values[i]);//Now check orderd invariantmultiset<MyClass,compare<std::greater<MyClass>>>::const_iteratornext(mset.cbegin()),it(next++);for(inti=0;i<99;++i,++it,++next)assert(*it>*next);}{//Now push the first and the last and insert the rest//before the last position using "insert_before"multiset<MyClass>mset;mset.insert_before(mset.begin(),values[0]);multiset<MyClass>::const_iteratorpos=mset.insert_before(mset.end(),values[99]);for(inti=1;i<99;++i)mset.insert_before(pos,values[i]);//Now check orderd invariantmultiset<MyClass>::const_iteratornext(mset.cbegin()),it(next++);for(inti=0;i<99;++i,++it,++next)assert(*it<*next);}return0;}
Для получения дополнительной информации о расширенных функциях поиска и вставки см. документацию ассоциативных контейнеров (например,<set>,<multiset>,<unordered_set>и<unordered_multiset>ссылки).
Статья Advanced lookup and insertion functions for associative containers раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 17. Boost.Intrusive может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.