Boost.Intrusive associative containers offer
the same interface as STL associative containers. However, STL and TR1 ordered
and unordered simple associative containers (std::set,
std::multiset, std::tr1::unordered_set and std::tr1::unordered_multiset) have some inefficiencies
caused by the interface: the user can only operate with value_type
objects. When using these containers we must use iteratorfind(constvalue_type&value) to find a value. The same happens in other
functions like equal_range,
lower_bound, upper_bound, etc.

However, sometimes the object to be searched is quite expensive to construct:

#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;}

Expensive is an expensive
object to construct. If "key" c-string is quite long Expensive has to construct a std::string
using heap memory. Like Expensive,
many times the only member taking part in ordering issues is just a small
part of the class. For example, with Expensive,
only the internal std::string is needed to compare the object.

In both containers, if we call get_from_set/get_from_unordered_set
in a loop, we might get a performance penalty, because we are forced to create
a whole Expensive object
to be able to find an equivalent one.

Sometimes this interface limitation is severe, because we might
not have enough information to construct the object but we might
have enough information to find the object.
In this case, a name is enough to search Expensive
in the container but constructing an Expensive
might require more information that the user might not have.

To solve this, set/multiset offer alternative functions,
which take any type comparable with the value and a functor that should be
compatible with the ordering function of the associative container. unordered_set/unordered_multiset
offers functions that take any key type and compatible hash and equality
functions. Now, let's see the optimized search function:

// 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;}

This new arbitrary key overload is also available for other functions taking
values as arguments:

A similar issue happens with insertions in simple ordered and unordered associative
containers with unique keys (std::set and
std::tr1::unordered_set).
In these containers, if a value is already present, the value to be inserted
is discarded. With expensive values, if the value is already present, we
can suffer efficiency problems.

set and unordered_set
have insertion functions to check efficiently, without constructing the value,
if a value is present or not and if it's not present, a function to insert
it immediately without any further lookup. For example, using the same Expensive class, this function can be inefficient:

if the insertion is possible (there is no equivalent value) insert_check collects all the needed
information in an insert_commit_data
structure, so that insert_commit:

does not execute further comparisons

can be executed with constant-time complexity

has no-throw guarantee.

These functions must be used with care, since no other insertion or erasure
must be executed between an insert_check
and an insert_commit pair.
Otherwise, the behaviour is undefined. insert_check
and insert_commit will come
in handy for developers programming efficient non-intrusive associative containers.
See set and unordered_set reference
for more information about insert_check
and insert_commit.

With multiple ordered and unordered associative containers (multiset
and unordered_multiset)
there is no need for these advanced insertion functions, since insertions
are always successful.

Some ordered associative containers offer low-level functions to bypass ordering
checks and insert nodes directly in desired tree positions. These functions
are provided for performance reasons when values to be inserted in the container
are known to fulfill order (sets and multisets) and uniqueness (sets) invariants.
A typical usage of these functions is when intrusive associative containers
are used to build non-intrusive containers and the programmer wants to speed
up assignments from other associative containers: if the ordering and uniqueness
properties are the same, there is no need to waste time checking the position
of each source value, because values are already ordered: back insertions
will be much more efficient.

Note: These functions don't
check preconditions so they must used with care. These are functions
are low-level operations will break container invariants
if ordering and uniqueness preconditions are not assured by the caller.

Let's see an example:

#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;}