Multiple index containers

"C++11/14 Advanced Programming: Exploring the Boost Library" Notes

Multi-index containers, as the name suggests, provide multiple ways to access elements.
Sometimes, you may want to implement different access order criteria for the same set of elements. For example, you want to traverse the elements in the list in a sequential manner, in a size sorting manner, or you want to search for a certain attribute of the element. . Pointer containers and intrusive containers can be solved to a certain extent. A pointer container can use a view allocator to establish a view of another container, thereby providing a new access method without changing the original container; intrusive containers directly modify the structure of the element. , you can add any number of hooks, each hook corresponds to an access interface.
But the limitation is: the pointer container requires that the element must be exclusive, and sometimes needs to meet the concept of cloning; and the intrusive container must modify the definition of the element, which is often not allowed.
The boost.multi_index library solves this type of problem by providing a multi-index container that can access the same set of elements stored in the container in different indexes.


Getting Started Example

Simple example
#include <boost/multi_index_container.hpp>      //多索引容器头文件
#include <boost/multi_index/ordered_index.hpp>  //有序索引
using namespace boost::multi_index;

int main()
{
    multi_index_container<int> mic;     //一个多索引容器,缺省使用有序索引
    assert(mic.empty());

    mic.insert(1);

    using namespace boost::assign;
    insert(mic)(2), 7, 6, 8;
    assert(mic.size() == 5);
    assert(mic.count(2) == 1);
    assert(mic.find(10) == mic.end());

    for (int i : mic)
    {
        std::cout << i << ',';  //顺序输出1,2,6,7,8
    }

    return 0;
}
Complex example
#include <boost/multi_index_container.hpp>      //多索引容器头文件
#include <boost/multi_index/ordered_index.hpp>  //有序索引
#include <boost/multi_index/hashed_index.hpp>   //散列(无序)索引
#include <boost/multi_index/key_extractors.hpp> //键提取器
using namespace boost::multi_index;

int main()
{
    typedef multi_index_container<int,          //多索引容器,容器类型int
            indexed_by<                         //使用index_by元函数定义多个索引
                ordered_unique<identity<int>>,  //第一个是有序单键索引
                hashed_unique<identity<int>>    //第二个是散列(无序)单键索引
                >
            >   mic_t;
    mic_t mic = { 2,1,7,6,8 };                  //多索引容器使用{...}初始化

    assert(mic.size() == 5);                    //默认第一个索引的接口
    assert(mic.count(2) == 1);
    assert(mic.find(10) == mic.end());

    //使用模板成员函数get()获取第二个索引,编号从0算起
    auto& hash_index = mic.get<1>();

    assert(hash_index.size() == 5);             //第二个索引的
    assert(hash_index.count(2) == 1);
    assert(hash_index.find(10) == hash_index.end());

    BOOST_FOREACH(int i,hash_index)             //同样可以使用foreach算法
    {
        std::cout << i << ',';                  //无序输出
    }

    return 0;
}

Index is a piece of metadata, which requires the use of a function object called a key extractor to obtain the key type used to generate the index. In this example, the element itself is used directly as the key, so identity is used. If you do not specify an index explicitly, the container will use the first index. If you want to obtain an index other than the first one, you must use the template member function get<N>(), which returns a reference
to a specific index type : mic_t::nth_index::type&, usually, you can use auto/decltype directly to simplify the declaration:

auto& hash_index = mic.get<1>();    //correct
auto  hash_index = mic.get<1>();    //error,不能声明索引实例变量
More complex example
#include <boost/multi_index_container.hpp>      //多索引容器头文件
#include <boost/multi_index/ordered_index.hpp>  //有序索引
#include <boost/multi_index/hashed_index.hpp>   //散列(无序)索引
#include <boost/multi_index/sequenced_index.hpp>//序列索引
#include <boost/multi_index/key_extractors.hpp> //键提取器
using namespace boost::multi_index;

class person :
    boost::less_than_comparable<person>         //使用operators库实现全序比较
{
public:
    int             m_id;
    std::string     m_fname, m_lname;

    person(int id, const std::string& f, const std::string& l) :
        m_id(id), m_fname(f), m_lname(l) {}

    const std::string& first_name() const { return m_fname; }
    std::string& last_name() { return m_lname; }

    friend bool operator<(const person& l,const person& r)
    {
        return l.m_id < r.m_id;
    }

    friend bool operator==(const person& l,const person& r)
    {
        return l.m_id == r.m_id;
    }

    friend std::size_t hash_value(const person& p)
    {
        size_t seed = 2016;
        hash_combine(seed, p.m_fname);
        hash_combine(seed, p.m_lname);
        return seed;
    }
};

int main()
{
    typedef multi_index_container<person,               //多索引容器,容器类型int
            indexed_by<                             //使用index_by元函数定义多个索引
                sequenced<>,                        //第一个是序列索引
                ordered_unique<identity<person>>,   //第二个是有序单键索引
                ordered_non_unique<                 //第三个是有序多键索引
                    member<person,std::string,&person::m_fname>>,   //使用成员变量
                hashed_unique<identity<person>>     //第四个是散列(无序)单键索引
                >
            >   mic_t;
    mic_t mic;                                      //声明多索引容器

    using namespace boost::assign;
    push_front(mic)                     //第一个索引是序列索引,所有可以用push_front
        (person(2, "agent", "smith"))   //插入四个元素
        (person(20, "lee", "someone"))  //顺序无关紧要
        (person(1, "anderson", "neo"))  //id不可重复
        (person(10, "lee", "bruce"));   //m_fname可重复

    auto& index0 = mic.get<0>();
    auto& index1 = mic.get<1>();
    auto& index2 = mic.get<2>();
    auto& index3 = mic.get<3>();

    //使用索引0,顺序输出元素,没有排序
    for (const person& p : index0)
    {
        std::cout << p.m_id << p.m_fname << ",";
    }

    //索引1,获取id最小的元素
    assert(index1.begin()->m_id == 1);

    //索引2,允许重复键的元素
    assert(index2.count("lee") == 2);

    //使用assign::insert在索引2上插入重复键的元素
    insert(index2)(person(30, "lee", "test"));
    assert(index3.size() == 5);

    //插入id重复的元素因索引1的限制而不能成功
    assert(!index2.insert(person(2, "lee", "test2")).second);

    return 0;
}

In this example, the first index of the container is sequenced, which does not sort the elements, so template parameters are usually not needed; the second index is ordered_unique, an ordered single key index, which requires the element to have operator<; the third index is ordered_non_unique , using a new key extractor member that is different from identity, similar in form to the member_hook of the intrusive container, you can use the member variables of the element as the key; the fourth index is hash_unique, an unordered single key index, which requires the element to satisfy boost. hash can calculate hash values.


Basic concepts in the multi_index library

1.Index

Index is the most important concept in the multi_index library. It is the interface for accessing elements in a multi-index container and determines the criteria by which external users perform read/write/search operations on elements. Different indexes have different usage interfaces, but the definition of the interface completely imitates the standard container and has most commonly used member functions, so it can be regarded as a standard container when used.
Indexes usually do not allow direct modification of elements, because an index container may hold multiple indexes, which are mutually restrictive, and certain operations cannot be performed.
Although the index is a certain type, it is defined as an internally used type. We cannot declare an index type variable by ourselves. The use of the index must be attached to the multi-index container, which is defined with the index specification in the template parameter of multi_index_container, and then Use the template member function get<N>() to obtain a reference to operate.

2. Index description

The index description is a high-level metadata that uses key extractors and other parameters to define the index. There are two meta-functions named node_class and index_class internally, which return the node type and index type that can be used by the container.
Currently, the multi_index library provides four types of index descriptions:

  • Sequence index: This type of index has only one sequenced, similar to the std::list doubly linked list sequence access interface.
  • Random access index: This type of index has only one random_access, which is similar to the sequence access interface of std::vector and provides random access capabilities in operator[] mode.
  • Ordered index: including ordered_unique and ordered_non_unique, an ordered collection access interface similar to std::set.
  • Hash (unordered) index: including hashed_unique and hashed_non_unique, an unordered set access interface similar to std::unordered_set.
3.Key Extractor

A key extractor is a single-argument function object that obtains the key used for indexing (sorting) from the element or the element's boost.ref wrapper, usually used as the first template argument of the index specification.
The multi_index library provides six key extractors:

  • identity: Use the element itself as the key
  • member: Use a public member variable of the element as a key
  • const_mem_fun: Use the return value of a const member function of the element as the key
  • mem_fun: Use the return value of a non-const member function of the element as the key
  • global_fun: Use the return value of a global function or static member function that operates on the element as the key
  • composite_key: The above key extractors can be combined into a new key

The key type obtained by the key extractor must be able to meet the requirements of the index. For example, an ordered index requires that the key defines a comparison operator, and a hash index requires that the key can calculate hash values ​​and perform equality comparisons.

4. Index description list

The index description list is the second template parameter of the multi-index container multi_index_container. It is implemented as an indexed_by structure and is used to define the index used by the multi-index container.
indexed_by is based on the metadata sequence mpl::vector in the template metaprogramming library mpl, which is a type of container that can accommodate multiple index descriptions. It uses preprocessing metaprogramming, and the current implementation is limited to a maximum of 20 metadata, which means that a maximum of 20 indexes can be used on a container at the same time.

5.Index tags

get<N>() obtains the index through the serial number, but this is inconvenient and difficult to remember, so the multi_index library provides the concept of index tags (tags), allowing the use of syntax tags to access the index.
Tag is an mpl type container that supports up to 20 types as index tags at the same time. The tag type can be defined arbitrarily. C++ built-in types int and std::string can also be used. However, duplicate tags are not allowed in a multi-index container.

ordered_unique<tag<struct id>,...>
hashed_unique<tag<int,short>,...>

auto& ordered_index = mic.get<id>();
auto& hashed_index = mic.get<int>();
6.Multiple index containers

The type of multi-index container is multi_index_container, and the declaration is:

template<
    typename Value,                                             //元素类型
    typename IndexSpecifierList =                               //索引说明列表
    indexed_by<ordered_unique<identity<Value> > >,  //缺省值
    typename Allocator = std::allocator<Value> >                    //内存分配器
class multi_index_container;

The class template parameters are element type, sorting criterion and allocator type, but the sorting criterion is very complicated. It is an index declaration list of indexed_by type. The index description list provides an ordered_unique by default, which means that if no index description is specified, the multi-index container The default behavior is the same as std::set, which is an ordered collection that does not allow duplication.


key extractor

1.Definition

A key extractor is a function object whose basic form is as follows:

struct some_key_extractor
{
    typedef T result_type;      //返回类型

    T& operator()(T& x)const;                           //操作原始类型
    T& operator()(const reference_wrapper<T>& x)const;  //操作引用包装类型

    template<typename ChainedPtr>
    Type& operator()(const ChainedPtr& x)const;         //操作链式指针类型
};

The operator() of the key extractor can not only operate on the type itself (T&), but also on objects wrapped by the boost.ref library (reference_wrapper<T>&), and it also supports "chained pointer" objects.
The so-called "chain pointer" refers to a series of combinations of "chain pointers" similar to pointer objects - including raw pointers, smart pointers, iterators and other types that have operator* that can perform dereference operations, T*, T* *, shared_ptr<T*> are all chain pointer types, and they can be dereferenced continuously and recursively to obtain a non-pointer type T& or reference_wrapper<T>&. This feature makes it easy to manipulate pointers, allowing multi-indexed containers to easily hold pointer type elements.
All predefined key extractors in the multi_index library are located in the header file <boost/multi_index/key_extractors.hpp>. If you want to reduce compilation time, you can include the necessary header files as appropriate.

2.identity

Identity is the simplest key extractor. It directly uses the element itself as the key without performing any extraction action. It is equivalent to the key type of a standard container. As long as the element type is not const, it is a writable key extractor.
identity is located in the header file <boost/multi_index/identity.hpp>, and the class summary is as follows:

template<class Type>
struct identity:
    mpl::if_c<
        is_const<Type>::value,
        detail::const_identity_base<Type>,
        detail::non_const_identity_base<Type>
    >::type
{};

Use the meta-function if_c to hand it over to const_identity_base and non_const_identity_base respectively according to whether the type Type is modified by const.
The main code of const_identity_base is as follows:

template<typename Type>
struct const_identity_base
{
    typedef Type result_type;   //返回类型定义

    //操作元素类型本身
    Type& operator()(Type& x)const
    {   return x;   }

    //操作元素类型的reference_wrapper包装
    Type& operator()(const reference_wrapper<Type>& x)const
    {   return x.get(); }

    //操作链式指针
    template<typename ChainedPtr>
    typename disable_if<
            is_convertible<const ChainedPtr&,Type&>,Type&>::type
    operator()(const ChainedPtr& x)const
    {   return operator()(*x);  }
};

The first two operator()s directly return the variable itself, and the last overloaded form is used to handle chain pointers and recursively generate a dereferenced operator(), so that the runtime can call it continuously until the final Type& type is obtained.
Identity is actually an ordinary function object, for example:

assert((is_same<string,identity<string>::result_type>::value));
assert(identity<int>()(10) == 10);
assert(identity<string>()("abc") == "abc");

int* p      = new int(100);         //指针
int** pp    = &p;                   //指针的指针(链式指针)
assert(identity<int>()(pp) == 100); //从链式指针中获取键
3.member

member is somewhat similar to the function of non-standard function object select1st, which can extract a public member variable in the type as a key. The interface implementation is basically the same as identity, and the meta-function if_c is also used. According to whether the type Type is modified by const, it is handed over to const_member_base and non_const_member_base for processing respectively. As long as the element type is not const, then it is a writable key extractor.
member has three template parameters, which are very similar in form to the member_hook option of the intrusive container. They specify the type Class to be extracted, the key type Type (that is, the member variable type of the type) and the member variable pointer PtrToMember, for example:

typedef pair<int,string> pair_t;
pair_t p(1,"one");

assert((member<pair_t,int,&pair_t::first>()(p)      == 1));
assert((member<pair_t,string,&pair_t::second>()(p)  == "one"));

person per(1,"anderson","neo");
assert((member<person,int,&person::m_id>()(per) == 1));

Some compilers with poor support for the C++ standard may not be able to use member. Multi_index provides an equivalent replacement: member_offset, which uses offsets instead of member pointers. In order to obtain maximum compatibility and ease of use, multi_index defines a macro BOOST_MULTI_INDEX_MEMBER, there is no need to manually write the declaration of the member variable pointer, member or member_offset is automatically selected according to the function of the compiler.

4.const_mem_fun

const_mem_fun uses the return value of a const member function in the type as a key, which is somewhat similar to the function object mem_fn, but it has two restrictions: it can only call const member functions, and this member function must be called without parameters.
const_mem_fun is a read-only key extractor located in the header file <boost/multi_index/mem_fun.hpp>. The class summary is as follows:

template<class Class,typename Type,
            Type (Class::*PtrToMemberFunction)()const>
struct const_mem_fun
{
    typedef typename remove_reference<Type>::type result_type;

    Type operator() (const Class& x)const
    {
        return (x.*PtrToMemberFunction)();  //调用无参const成员函数
    }

    ...     //其他operator()定义
};

Its template parameters are similar to member, but the last parameter is a member function pointer, and the Class and Type parameters must exactly match the member function pointer, for example:

string str("abc");
typedef const_mem_fun<string,size_t,&string::size > cmf_t;
assert(cmf_t()(str) == 3);

person per(1,"anderson","neo");
typedef const_mem_fun<person,const string& &person::first_name> cmf_t2;
assert(cmf_t2()(per) == "anderson");

//下面两行会因为类型错误无法编译
typedef const_mem_fun<const person,const string& &person::first_name> cmf_t2;
typedef const_mem_fun<person,string& &person::first_name> cmf_t2;

const_mem_fun also has a macro BOOST_MULTI_INDEX_CONST_MEM_FUN for masking compiler differences.

5.mem_fun

mem_fun is similar to const_mem_fun, except that it uses the return value of a non-const member function of the element as the key. It is a read-only key extractor, and the header file location is the same as const_mem_fun.
It should be noted that mem_fun has the same name as the function object std::mem_fun in the standard library. When using it, be sure to add the namespace boost::multi_index limit. You can also use the macro BOOST_MULTI_INDEX_MEM_FUN, so you don't have to consider namespace issues.

6.global_fun

global_fun uses a global function or static member function to operate elements, using the return value of the function as the key. It is similar to identity and supports const or non-const functions.
global_fun is a read-only key extractor located in the header file <boost/multi_index/global_fun.hpp>. The class summary is as follows:

template<class Value,typename Type,Type (*PtrToFunction)(Value)>
struct global_fun:
    mpl::if_c<...>::type
{};

The usage is similar to const_mem_fun and mem_fun, except that the last template parameter must be a function pointer with a parameter of type Value.

//定义一个person类为参数类型的全局函数
string nameof(const person& p)
{
    return p.m_fname + " " + p.m_lname;
}
//使用global_fun
person per(1,"anderson","neo");
typedef global_fun<const person&,string,&nameof> gf_t;
assert(gf_t()(per) == "anderson neo");

//下面两行类型不匹配无法编译通过
typedef global_fun<person&,string,&nameof> gf_t;
typedef global_fun<const person&,string&,&nameof> gf_t;
7. Custom Key Extractor

The key extractor is essentially a single-parameter function object, so you can write it completely by yourself without using the key extractor predefined by the multi_index library, as long as the definition of the key extractor is met.
The custom key extractor must first meet the requirements of the standard function object, and the definition contains the internal type result_type, which is also the key type. Then, to implement the operator() that operates on the element type, it must be a const member function. Several overloads for T&, reference_wrapper<T>&, and ChainedPtr& can be implemented as needed, but not all of them need to be implemented.
For example, you can write a key extractor person_name to achieve the equivalent function of global_fun<const person&,string,&nameof>:

struct person_name
{
    typedef string result_type;                     //返回值类型定义,必需
    result_type operator()(const person& p)const    //必须为const
    {
        return p.m_fname + " " + p.m_lname;
    }
    result_type operator()(person *const p)const    //支持容纳原始指针
    {
        return p->m_fname + " " + p->m_lname;
    }
}

sequence index

Sequence index is the simplest kind of index. It does not actually perform any indexing operation on the elements, but only stores the elements in sequence. The sequence index is located in the header file <boost/multi_index/sequenced_index.hpp>.
The index description of the sequence index is sequenced, and its class summary is as follows:

template <typename TagList = tag<> >
struct sequenced
{
    template<typename SuperMeta>
    struct index_class
    {
        typedef detail::sequenced_index<...> type;
    };
};

Because sequence indexes are not sorted based on keys, sequenced does not use any key extractors, only a TagList template parameter, which is used to label the index syntax.
The class detail::sequenced_index used by sequence index provides doubly linked list operations similar to std::list, but some interfaces are constant and elements cannot be modified at will.

class sequenced_index
{
public:
    typedef some_define     value_type;
    typedef some_define     iterator;
    typedef iterator        const_iterator;
    ...

    //赋值操作
    sequenced_index&        operator=(const sequenced_index& x);
    void                    assign(InputIterator first,InputIterator last);
    void                    assign(size_type n,const value_type& value);

    //迭代器操作
    iterator                begin();
    iterator                end();
    iterator                iterator_to(const value_type& x);

    //元素访问
    const_reference          front()const;
    const_reference          back()const;
    std::pair<iterator,bool> push_front(const value_type& x);
    void                     pop_front();
    std::pair<iterator,bool> push_back(const value_type& x);
    void                     pop_back();

    std::pair<iterator,bool> insert(iterator position,const value_type& x);
    void                     insert(iterator position,size_type n,const value_type& x);
    iterator                 erase(iterator position);
    iterator                 erase(iterator first,iterator last);
    ...                      //其他remove()、unique()、splice()、sort()操作

    ...                      //各种比较操作定义
}

The interface of sequence_index is basically the same as list. The main points to note are:

  • Indexes do not provide public constructors and analysis functions (handled by multi-index containers), but you can use operator= and assign() to assign values. The usage is the same as that of standard containers.
  • Dereference iterators (begin(), rbegin(), etc.) return const types, and elements cannot be modified using iterators.
  • The front() and back() functions that access elements return const references and cannot modify the elements.
  • Because there may be other index constraints, push_front(), push_back() and insert() may all fail, so the return value is the same pair as set::insert(), and the second member indicates whether the operation is successful.
  • Similar to intrusive containers, indexes provide the iterator_to() function, which can obtain the corresponding iterator from the reference of an element in the container.

usage:

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/assign.hpp>
using namespace boost::multi_index;
using namespace std;

int main()
{
    typedef multi_index_container<int,
        indexed_by<sequenced<
                    tag<int,struct int_seq> > >
        > mic_t;

    using namespace boost::assign;
    mic_t mic = (list_of(2),3,5,7,11);
    assert(!mic.empty() && mic.size() == 5);

    assert(mic.front() == 2);
    assert(mic.back() == 11);

    assert(mic.push_front(2).second);
    assert(mic.push_back(19).second);

    auto& seq_index = mic.get<int_seq>();   //使用标签获得索引
    seq_index.insert(seq_index.begin(),5,100);  //插入5个元素
    assert(std::count(seq_index.begin(),mic.end(),100) == 5);

    seq_index.unique();
    assert(std::count(seq_index.begin(),mic.end(),100) == 1);

    //支持容器间的比较操作
    mic_t mic1 = (list_of(2),3,5,7,11);
    mic_t mic2 = mic1;
    assert(mic1 == mic2);

    mic2.push_back(3);
    assert(mic1<mic2);

    // *seq_index.begin() = 9;  //编译出错,不能修改元素的值,报const错

    auto& x = mic.front();
    assert(mic.begin() == mic.iterator_to(x));
}

random access index

Random access index is another kind of sequence index, which also stores elements sequentially, but provides more access interfaces than sequence index.
The random access index is located in the header file <boost/multi_index/random_access_index.cpp>.
Its index specification is random_access, which is very similar to sequenced. It also does not use a key extractor and has only a label template parameter.
The class used for random access index is detail::random_access_index, which is a superset of sequenced_index and has all the functions of sequenced_index. It has two more operators[] and at() that can randomly access elements at any position. The other interfaces are the same, but The time complexity of the operations varies.
Although random_access_index is very similar to std::vector in use, it is essentially a chain container and does not provide continuous element storage like std::vector.
usage:

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/assign.hpp>
using namespace boost::multi_index;
using namespace std;

int main()
{
    typedef multi_index_container<int,
        indexed_by<random_access<> >
        > mic_t;

    using namespace boost::assign;
    mic_t mic1 = (list_of(2),3,5,7,11);

    assert(mic1[0] == 2);
    assert(mic1.at(2) == 5);

    mic1.erase(boost::next(mic1.begin(),2));
    assert(mic1[2] == 7);

    mic_t mic2;
    mic2.splice(mic2.end(),mic1);               //使用链表的接合操作
    assert(mic1.empty() && mic2.size() == 4);

    push_front(mic1)(8),10,20,16;

    mic1.sort();
    mic2.sort();

    mic1.merge(mic2);

    //逆序输出元素
    for(auto itr = mic1.rbegin();itr!=mic1.rend();++itr)
    {
        std::cout << *itr << ",";
    }
}

Although random_access_index provides the function of randomly accessing elements, because its interfaces are constant, many standard algorithms that require the use of iterator assignment operations (such as sorting algorithms and replacement algorithms) cannot be used:

std::sort(mic1.begin(),mic1.end());             //编译出错
std::random_shuffle(mic1.begin(),mic1.end());   //编译出错
std::replace(mic1.begin(),mic1.end(),2,222);    //编译出错

ordered index

The ordered index sorts elements based on key extractors and uses a red-black tree structure to provide an ordered set access interface similar to set.
The index description of the ordered index located in the header file <boost/multi_index/ordered_index.hpp> includes ordered_unique and ordered_non_unique. The former does not allow duplicate keys, and the latter allows duplicates. Both declarations and interfaces are the same. Taking ordered_unique as an example, the class summary is as follows :

template<typename Arg1,typename Arg2=mpl::na,typename Arg3=mpl::na>
struct ordered_unique
{
    template<typename SuperMeta>
    struct index_class
    {
        typedef detail::ordered_index<...> type;
    };
};

It has three template parameters. Because it uses template metaprogramming technology, it can work by providing at least one template parameter.

  • The first parameter can be a label or key extractor and must be provided
  • If the first is a label, then the second argument must be a key extractor
  • The last parameter is the comparison predicate object. The default is std::less<typename KeyFromValue::result_type>, which is the less than comparison of the key extractor.

The class used by ordered index is detail::ordered_index, and the interface is similar to std::set.

template<typename KeyFromValue,typename Compare,...>
class ordered_index
{
public:
    typedef some_define     key_type;
    typedef some_define     value_type;
    typedef some_define     iterator;
    typedef iterator        const_iterator;
    ...

    //赋值操作
    ordered_index&      operator=(const ordered_index& x);

    //迭代器操作
    iterator                begin();
    iterator                end();
    iterator                iterator_to(const value_type& x);

    //元素访问
    std::pair<iterator,bool> insert(const value_type& x);
    void                     insert(iterator position,const value_type& x);
    iterator                 erase(iterator position);

    //有序相关操作
    iterator                 find(const CompatibleKey& x)const;
    iterator                 find(const CompatibleKey& x,const CompatibleCompare& comp)const;

    size_type                count(const CompatibleKey& x)const;
    size_type                count(const CompatibleKey& x,const CompatibleCompare& comp)const;

    iterator                 upper_bound(const CompatibleKey& x)const;
    iterator                 upper_bound(const CompatibleKey& x,const CompatibleCompare& comp)const;

    std::pair<iterator,iterator> equal_range(const CompatibleKey& x)const;
    std::pair<iterator,iterator> equal_range(const CompatibleKey& x,const CompatibleCompare& comp)const;
    std::pair<iterator,iterator> range(LowerBounder lower,UpperBounder upper)const;//参数为两个函数对象
};

The usage is not much different from std::set. Note that iterators cannot be used to modify elements.

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/assign.hpp>
#include <boost/foreach.hpp>
#include <string>
using namespace boost::multi_index;
using namespace std;

int main()
{
    //单键有序索引容器
    typedef multi_index_container<int,
        indexed_by<
            ordered_unique<identity<int>> 
            >
        > mic_t1;

    using namespace boost::assign;
    mic_t1 mic1;
    insert(mic1)(2),3,5,7,11;

    assert(mic1.size() == 5);
    assert(!mic1.insert(3).second);
    assert(mic1.find(7) != mic1.end());

    //多键有序索引容器
    typedef multi_index_container<string,
        indexed_by<
            ordered_non_unique<
                BOOST_MULTI_INDEX_CONST_MEM_FUN(string,size_t,size)>
            >
        > mic_t2;
    mic_t2 mic2;
    insert(mic2)("111")("22")("333")("4444");//重复元素
    assert(mic2.count(3) == 2);             //两个重复元素

    //使用equal_range()输出重复元素,不能用for
    BOOST_FOREACH(auto& str,mic2.equal_range(3))
    {
        std::cout << str << ",";
    }
}

There are two high-tech uses of ordered indexes, compatible key comparison and obtaining range intervals, taking the person class as an example.
Compatible key comparison , the so-called compatible key refers to a type different from the key itself in the index description, but its comparison effect is the same as the key. For example, in the person class, the key type defined by identity<person> is person, but its comparison operator operator< uses the m_id member variable of type int, so int is its compatible key, and it is obvious to construct an int type The cost is much lower than the cost of constructing a person type, and the efficiency will naturally increase.
In order to use the compatible key comparison function, you need to define a predicate for the person class that compares with the int type:

struct compare_by_id
{
    typedef bool result_type;
    bool operator()(const person& p,int id)const
    {
        return p.m_id < id;
    }
    bool operator()(int id,const person& p )const
    {
        return id < p.m_id;
    }
};

This eliminates the need to use the entire element to perform the comparison when using:

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/assign.hpp>
#include <boost/foreach.hpp>
#include <boost/functional/hash.hpp>        //hash_combine函数
#include <string>
using namespace boost::multi_index;
using namespace std;

class person :
    boost::less_than_comparable<person>         //使用operators库实现全序比较
{
public:
    int             m_id;
    std::string     m_fname, m_lname;

    person(int id, const std::string& f, const std::string& l) :
        m_id(id), m_fname(f), m_lname(l) {}

    const std::string& first_name() const { return m_fname; }
    std::string& last_name() { return m_lname; }

    friend bool operator<(const person& l,const person& r)
    {
        return l.m_id < r.m_id;
    }

    friend bool operator==(const person& l,const person& r)
    {
        return l.m_id == r.m_id;
    }

    friend std::size_t hash_value(const person& p)
    {
        size_t seed = 2016;
        boost::hash_combine(seed, p.m_fname);
        boost::hash_combine(seed, p.m_lname);
        return seed;
    }
};

struct compare_by_id
{
    typedef bool result_type;
    bool operator()(const person& p,int id)const
    {
        return p.m_id < id;
    }
    bool operator()(int id,const person& p )const
    {
        return id < p.m_id;
    }
};

int main()
{
    //单键有序索引容器
    typedef multi_index_container<person,
        indexed_by<
            ordered_unique<identity<person>> 
            >
        > mic_t;

    using namespace boost::assign;
    mic_t mic;
    insert(mic)
        (person(2,"agent","smith"))
        (person(20,"lee","someone"))
        (person(1,"anderson","neo"))
        (person(10,"lee","bruce"));

    //构造一个大对象执行比较操作,成本很高
    assert(mic.count(person(1,"abc","xby")) == 1);

    //使用自定义的兼容键比较谓词
    assert(mic.count(1,compare_by_id()) == 1);
    assert(mic.find(10,compare_by_id()) != mic.end());
}

To obtain the range interval , the member function range() is used, which is a template function. The parameters (LowerBounder and UpperBounder) are two predicate functions or function objects with keys as parameters, which are used to determine the lower and upper bounds of the interval, which is equivalent to a < x && x < b, which is more convenient and intuitive than using lower_bound() and upper_bound() directly.
For example, define a 2 <= p.m_id < 20 left-closed and right-open interval for person's id. The usage of lower_bound() and upper_bound() is as follows:

//获得id>=2的下界
mic_t::iterator l = mic.lower_bound(2,compare_by_id());
//获得id>=20的下界,即<20的上界
mic_t::iterator u = mic.lower_bound(20,compare_by_id());

//foreach循环,使用make_pair构造一个迭代器区间输出元素
BOOST_FOREACH(const person& p,std::make_pair(l,u))
{
    std::cout << p.m_id << ":" << nameof(p) << endl;
}

This way of writing is easy to confuse, and it is difficult to grasp the endpoint conditions. It is much simpler to use the range() function of the ordered index, and the upper and lower bound function objects can be clearly implemented:

struct lower_bounder                    //定义p>=2
{
    typedef bool result_type;
    bool operator()(const person& p)
    {   return p.m_id >= 2; }
};

struct upper_bounder                    //定义p<20
{
    typedef bool result_type;
    bool operator()(const person& p)
    {   return p.m_id < 20; }
};
//调用range()获取这个区间
BOOST_FOREACH(const person& p,mic.range(lower_bounder(),upper_bounder()))
{
    std::cout << p.m_id << ":" << nameof(p) << endl;
}

Using C++11/14 lambda expressions can simplify the above tedious writing of upper and lower bound predicates and generate anonymous function objects:

BOOST_FOREACH(const person& p,mic.range(
    [](const person& p){ return p.m_id >= 2; },     //使用C++11/14的lambda
    [](const person& p){ return p.m_id < 20; }))

The ordered index also defines a special predicate function unbounded, which can be used at any endpoint of the interval to indicate that the endpoint is unlimited:

mic.range(lower_bounder(),unbounded);   //p.m_id >= 2
mic.range(unbounded,upper_bounder());   //p.m_id < 20
mic.range(unbounded,unbounded);         //所有元素

Hash index

A hash index hashes based on the key extractor (of the element) and provides an unordered set interface similar to std::unordered_set. The hash index is located in the header file <boost/multi_index/hashed_index.hpp>.
The index description of the hash index includes hashed_unique and hashed_non_unique. The former does not allow duplicate keys and the latter allows duplicate keys. The declaration and interface of both are the same. Take hashed_unique as an example. :

template<typename Arg1,typename Arg2,typename Arg3,typename Arg4>
struct hashed_uniqued
{
    template<typename SuperMeta>
    struct  index_calss
    {
        typedef detail::hashed_index<...> type;
    };
};

It provides four template parameters and uses the same template metaprogramming technology as ordered_unique. It can work by providing at least one template parameter.

  • The first argument can enable a label or key extractor and must be provided
  • If the first is a label, then the second argument must be a key extractor
  • The parameter after the key extractor is the hash function object, the default is boost::hash<typename KeyFromValue::result_type>
  • The last parameter is the equality comparison predicate object, the default is std::equal_to<typename KeyFromValue::result_type>

The class used by the hash index is hashed_index. The interface is similar to std::unordered_set. The class summary is as follows:

template<typename KeyFromValue,typename Hash,typename Pred,...>
class hashed_index
{
public:
    typedef some_define     key_type;
    typedef some_define     value_type;
    typedef some_define     iterator;
    typedef iterator        const_iterator;
    ...

    //赋值操作
    hashed_index&           operator=(const hashed_index& x);

    //迭代器操作
    iterator                begin();
    iterator                end();
    iterator                iterator_to(const value_type& x);

    //元素访问
    iterator                 find(const CompatibleKey& x)const;
    iterator                 find(const CompatibleKey& x,
                                const CompatibleHash& hash,
                                const CompatiblePred& eq)const;

    size_type                count(const CompatibleKey& x)const;
    size_type                count(const CompatibleKey& x,
                                const CompatibleHash& hash,
                                const CompatiblePred& eq)const;

    std::pair<iterator,iterator> equal_range(const CompatibleKey& x)const;
    std::pair<iterator,iterator> equal_range(const CompatibleKey& x,
                                            const CompatibleHash& hash,
                                            const CompatiblePred& eq)const;
};

The interface of hashed_index is similar to that of unordered_set. Because it is unordered, it does not provide member functions lower_bound(), upper_bound() and range(). In addition, hashed_index also has some special member functions related to the hash container, such as the number of buckets, load factor, etc.
Usage hash index is like std::unordered_set:

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/assign.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/functional/hash.hpp>        //hash_combine函数
#include <string>
using namespace boost::multi_index;
using namespace std;

class person :
    boost::less_than_comparable<person>         //使用operators库实现全序比较
{
public:
    int             m_id;
    std::string     m_fname, m_lname;

    person(int id, const std::string& f, const std::string& l) :
        m_id(id), m_fname(f), m_lname(l) {}

    const std::string& first_name() const { return m_fname; }
    std::string& last_name() { return m_lname; }

    friend bool operator<(const person& l,const person& r)
    {
        return l.m_id < r.m_id;
    }

    friend bool operator==(const person& l,const person& r)
    {
        return l.m_id == r.m_id;
    }

    friend std::size_t hash_value(const person& p)
    {
        size_t seed = 2016;
        boost::hash_combine(seed, p.m_fname);
        boost::hash_combine(seed, p.m_lname);
        return seed;
    }
};

string nameof(const person& p)
{
    return p.m_fname + " " + p.m_lname;
}

int main()
{
    typedef multi_index_container<person,
        indexed_by<
            hashed_unique<identity<person>> 
            >
        > mic_t;

    using namespace boost::assign;
    mic_t mic;
    insert(mic)
        (person(2,"agent","smith"))
        (person(1,"anderson","neo"))
        (person(10,"lee","bruce"));

    assert(mic.size() == 3);
    assert(mic.find(person(1,"anderson","neo")) != mic.end());

    //散列索引同样可以使用兼容键来查找元素,但需要两个函数对象进行散列和相等比较,比有序索引要多做一些工作
    //person类散列使用了m_fname和m_lname,相等比较使用了m_id,涉及元素较多,所以使用一个boost::tuple来定义兼容键
    typedef boost::tuple<int,string,string> hash_key_t;

    //定义散列函数对象
    struct hash_func
    {
        typedef size_t result_type;
        size_t operator()(const hash_key_t& k)const
        {
            size_t seed = 2016;
            boost::hash_combine(seed,k.get<1>());
            boost::hash_combine(seed,k.get<2>());
            return seed;
        }
    };

    //定义相等比较函数对象
    struct equal_func
    {
        typedef bool result_type;
        bool operator()(const hash_key_t& k,const person& p)const
        {   return k.get<0>() == p.m_id;    }
        bool operator()(const person& p,const hash_key_t& k)const
        {   return k.get<0>() == p.m_id;    }
    };

    assert(mic.count(boost::make_tuple(1,"anderson","neo"),hash_func(),equal_func()) == 1);
    assert(mic.find(boost::make_tuple(10,"lee","bruce"),hash_func(),equal_func()) != mic.end());

    return 0;
}

Modify elements

The iterator interfaces of all indexes in the multi_inde library are constant and are not allowed to be modified directly by users, because a change operation may cause other indexes to be inconsistent. For this reason, multi_index uses another mechanism. All indexes provide two dedicated Member functions for modifying elements: replace() and modify(), ordered indexes and hash indexes also provide a special member function modify_key() that modifies the key. These operations can keep the state of the multi-index container from being destroyed.
Replacing elements
The member function replace() can replace the value of an element at a valid iterator position, and other indexes remain updated synchronously.
You can usually use the find() algorithm or find() member function to obtain the iterator position, and then call to replace bool replace(iterator position,const value_type&x);the element. If you use iterator_to(), you need to be careful. Using an equivalent copy of the element will result in an invalid iterator, and using replace() will cause a runtime exception.
Example:

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/assign.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/functional/hash.hpp>        //hash_combine函数
#include <string>
using namespace boost::multi_index;
using namespace std;

class person :
    boost::less_than_comparable<person>         //使用operators库实现全序比较
{
public:
    int             m_id;
    std::string     m_fname, m_lname;

    person(int id, const std::string& f, const std::string& l) :
        m_id(id), m_fname(f), m_lname(l) {}

    const std::string& first_name() const { return m_fname; }
    std::string& last_name() { return m_lname; }

    friend bool operator<(const person& l,const person& r)
    {
        return l.m_id < r.m_id;
    }

    friend bool operator==(const person& l,const person& r)
    {
        return l.m_id == r.m_id;
    }

    friend std::size_t hash_value(const person& p)
    {
        size_t seed = 2016;
        boost::hash_combine(seed, p.m_fname);
        boost::hash_combine(seed, p.m_lname);
        return seed;
    }
};

string nameof(const person& p)
{
    return p.m_fname + " " + p.m_lname;
}

struct compare_by_id
{
    typedef bool result_type;
    bool operator()(const person& p,int id)const
    {
        return p.m_id < id;
    }
    bool operator()(int id,const person& p )const
    {
        return id < p.m_id;
    }
};

int main()
{
    typedef multi_index_container<person,
        indexed_by<
            ordered_unique<identity<person>>,   //单键有序索引
            ordered_non_unique<                 //有序多键索引
                member<person,string,&person::m_fname>>,
            hashed_unique<identity<person>>     //散列单键索引
            >
        > mic_t;

    using namespace boost::assign;
    mic_t mic;
    insert(mic)
        (person(2,"agent","smith"))
        (person(20,"lee","someone"))
        (person(1,"anderson","neo"))
        (person(10,"lee","bruce"));

    auto pos = mic.find(20,compare_by_id());    //查找id为20的元素
    assert(pos != mic.end());

    mic.replace(pos,person(20,"lee","long"));   //替换这个元素
    assert(pos->m_lname == "long");

    mic.replace(pos,person(15,"lee","long"));   //修改元素的键

    //如果修改后元素与索引约束发生冲突则会导致替换失败,函数返回false
    assert(!mic.replace(pos,person(2,"lee","someone")));    //索引0冲突
    assert(!mic.replace(pos,person(10,"lee","bruce")));     //索引2冲突

    return 0;
}

Although the replace() member function of the modified element
provides a strong exception safety guarantee, a complete temporary element object must be constructed during the operation, and the operation cost is high. modify() is another method of modifying elements. It uses a function or function object called a modifier, accepts a reference to an element as a parameter, and can only change a certain component of the element. It is a lightweight modification element. method.
The statement of modify is as follows:

template<typename Modifier>
bool modify(iterator position,Modifier mod);

It has two parameters, the first is the iterator position to be modified, the same as replace(), and the second is the modifier object to perform the modification operation of the element.
For example, if we want to modify each member variable of person, we can use the lambda expression of C++11/14 to write the modifier directly in modify():

mic.modify(pos,
        [](person& p){ p.m_id = 15; });
assert(pos->m_id == 15);

mic.modify(pos,
        [](person& p){ p.m_fname = "mike"; });
assert(pos->m_fname == "mike");

mic.modify(pos,
        [](person& p){ p.m_lname = "david"; });
assert(pos->m_lname == "david");

Although modify() avoids the cost of constructing temporary objects and has high execution efficiency, it cannot guarantee the safety of the operation. If the element is modified and conflicts with the constraints of the index, the modification will fail and the element will be deleted !

auto pos = mic.find(20,compare_by_id());    //找到id为20的元素

//将id修改为1,与索引0的约束(ordered_unique)发生冲突,修改失败,元素被删除
assert(!mic.modify(pos,[](person& p){ p.m_id = 1; }));

//此时元素已被删除,无法找到
assert(mic.size() == 3);
assert(mic.find(20,compare_by_id()) == mic.end());

In order to solve this phenomenon, modify() provides an overloaded form similar to the database rollback mechanism, allowing users to use a "rollback" function or function object to restore the original value when the modification fails. This form of The modify() function prototype is as follows:

template<typename Modifier,typename Rollback>
bool modify(iterator position,Modifier mod,Rollback back);

Usage is as follows:

auto tmp = pos->m_id;               //修改前先保存原值
assert(!mic.modify(pos,
    [](person& p){ p.m_id = 1;},
    [](person& p){ p.m_id = tmp;})); //回滚操作,修改失败回滚恢复原值
assert(mic.size() == 4);
assert(mic.find(20,compare_by_id()) != mic.end());

Modify keys:
Ordered indexes and hash indexes have a special modification function modify_key(), which can directly modify the keys used by the index instead of the elements themselves. Using modify_key() requires that the index's key extractor must be writable.
The declaration of modify_key is as follows:

template<typename Modifier>
bool modify_ke(iterator position,Modifier mod);

template<typename Modifier,typename Rollback>
bool modify_key(iterator position,Modifier mod,Rollback back);

Since the modifier of modify_key() operates not on the type of the element itself, but on the key type, the key type must be used as the parameter when writing lamba expressions:

auto& index = mic.get<1>();     //获取索引
auto pos = index.find("agent"); //查找元素

index.modify_key(pos,
    [](string& str){ str = "virus"; });
assert(pos->m_fname == "virus");

//同样,如果modify_key()修改键后导致索引冲突,那么元素也会被删除,使用回滚操作避免
auto tmp = pos->m_fname;
index.modify_key(pos,
    [](string& str){ str = "virus"; },
    [](string& str){ str = tmp; });

Multiple index containers

Key extractors and indexes were introduced earlier, this section introduces true multi-index containers.
The forward declaration of the multi-index container is listed above, and its class summary:

template<
    typename Value,                                 //元素类型
    typename IndexSpecifierList=indexed_by<...>,    //索引说明列表
    typename Allocator=std::allocator<Value> >      //内存分配器
class multi_index_container:
    public detail::multi_index_base_type<...>::type
{
public:
    //构造函数、赋值操作
    multi_index_container(InputIterator first,InputIterator last);
    multi_index_container(const multi_index_container& x);
    multi_index_container& operator=(const multi_index_container& x);

    //索引类型定义
    template<int N> struct          nth_index;
    template<typename Tag> struct   index;

    //获取索引操作
    template<int N>
    typename nth_index<N>::type&    get();
    template<typename Tag>
    typename index<Tag>::type&      get();

    //投射操作
    template<int N,typename IteratorType>
    typename nth_index<N>::type::iterator   project(IteratorType it);
    template<typename Tag,typename IteratorType>
    typename index<Tag>::type::iterator     project(IteratorType it);
};

Essentially, multi_index_container is an index container. It is only responsible for managing the index, and each index is responsible for the management of elements.
get<N>() is the most important member function. It has two overloaded forms. You can use integer serial number or type tag to get the index. Therefore, there are also two types of index, namely nth_index<N> and index<tag>. , these two are actually meta-functions. You need to use the form of ::type to obtain the real index type. Usually, auto/decltype is used to simply avoid the problem of type declaration.
project<N>() is an iterator used to convert between multiple different indexes. It can project the iterator of one index into the iterator of another index, and the two iterators point to the same element.
multi_index_container is serializable. If not needed, you can define the macro BOOST_MULTI_INDEX_DISABLE_SERIALIZATION to disable the serialization code and speed up compilation.
Example usage of a multi-index container with eight indexes:

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/global_fun.hpp>
#include <boost/assign.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/functional/hash.hpp>        //hash_combine函数
#include <string>
using namespace boost::multi_index;
using namespace std;

class person :
    boost::less_than_comparable<person>         //使用operators库实现全序比较
{
public:
    int             m_id;
    std::string     m_fname, m_lname;

    person(int id, const std::string& f, const std::string& l) :
        m_id(id), m_fname(f), m_lname(l) {}

    const std::string& first_name() const { return m_fname; }
    std::string& last_name() { return m_lname; }

    friend bool operator<(const person& l,const person& r)
    {
        return l.m_id < r.m_id;
    }

    friend bool operator==(const person& l,const person& r)
    {
        return l.m_id == r.m_id;
    }

    friend std::size_t hash_value(const person& p)
    {
        size_t seed = 2016;
        boost::hash_combine(seed, p.m_fname);
        boost::hash_combine(seed, p.m_lname);
        return seed;
    }
};

string nameof(const person& p)
{
    return p.m_fname + " " + p.m_lname;
}

struct person_name
{
    typedef string result_type;                     //返回值类型定义,必需
    result_type operator()(const person& p)const    //必须为const
    {
        return p.m_fname + " " + p.m_lname;
    }
    result_type operator()(person *const p)const    //支持容纳原始指针
    {
        return p->m_fname + " " + p->m_lname;
    }
};

struct compare_by_id
{
    typedef bool result_type;
    bool operator()(const person& p,int id)const
    {
        return p.m_id < id;
    }
    bool operator()(int id,const person& p )const
    {
        return id < p.m_id;
    }
};

typedef sequenced<tag<int,struct seq_idex> >        idx_sf0;    //序列索引 类似std::list一样双向链表
typedef random_access<tag<struct rnd_idx,string> >  idx_sf1;    //随机访问索引 类似std::vector一样随机访问序列
typedef ordered_unique<identity<person>>            idx_sf2;    //有序单键索引 基于m_id从小到大排序的不允许重复有序集合
typedef ordered_non_unique<                                     //有序多键索引 基于m_fname从小到大排序的允许重复有序集合
    BOOST_MULTI_INDEX_MEMBER(person,string,m_fname)> idx_sf3;
typedef ordered_unique<                                         //有序单键索引 基于m_fname从大到小排序的不允许重复有序集合
    BOOST_MULTI_INDEX_CONST_MEM_FUN(
        person,const string&,first_name),
    std::greater<const string> >                    idx_sf4;    //使用大于比较排序
typedef hashed_unique<                                          //散列单键索引 基于m_lname的不允许重复的无序集合
    BOOST_MULTI_INDEX_MEMBER(person,string,m_lname)> idx_sf5;
typedef hashed_non_unique<                                      //散列多键索引 基于m_fname+m_lname的允许重复的无序集合
    global_fun<const person&,string,&nameof>>       idx_sf6;
typedef hashed_unique<person_name>                  idx_sf7;    //散列多键,使用自定义键提取器(基于m_fname+m_lname的不允许重复的无序集合)

int main()
{
    typedef multi_index_container<person,
        indexed_by<
            idx_sf0,idx_sf1,            //序列索引和随机访问索引
            idx_sf2,idx_sf3,idx_sf4,    //有序索引
            idx_sf5,idx_sf6,idx_sf7>    //散列索引
    > mic_t;

    using namespace boost::assign;
    mic_t mic;
    push_back(mic)
        (person(2,"agent","smith"))     //成功
        (person(20,"lee","someone"))    //成功
        (person(1,"anderson","neo"))    //成功
        (person(10,"lee","bruce"));     //因为m_fname重复所以插入失败

    assert(mic.size() == 3);            //只插入了3个元素

    auto& idx1 = mic.get<rnd_idx>();    //获取随机访问索引
    auto& idx2 = mic.get<2>();          //获取有序索引

    auto pos = idx2.find(1,compare_by_id());    //在有序索引中查找元素
    auto pos2 = mic.project<string>(pos);       //投射到随机访问索引

    assert(pos2 == idx1.iterator_to(idx1[2]));

    return 0;
}

composite index key

Similar to the concept of joint primary keys in the database, sometimes it may not be enough to sort elements using only one key. You need to find elements based on multiple keys. The multi_index library provides a composite index key composite_key, which can combine multiple key extractors into a new key. For index use, located in the header file <boost/multi_index/composite_key.hpp>
composite_key is a read-only extraction key that implements a combination of multiple key extractors based on boost.tuple. Its first template parameter is the element type Value. Can be followed by one or more combined key extractors. These extractors can be identity, member, const_mem_fun, etc., but they must also use Value as the element type. Currently, up to ten key extractors are supported in combination.
The return type of composite_key's operator() is composite_key_result, which is a simple struct that overloads the hash operations of the standard comparison predicates equal_to, less, greater, and boost, and supports comparison operations with tuples, and can be used like basic types.
Usage:
composite_key can be used for ordered indexes and hash indexes. For example, use composite_key to define a composite index key based on m_id and m_fname. Note that the element type uses pointers, which means that the pointer type must be stored in the container:

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/assign.hpp>
#include <boost/assign/ptr_list_inserter.hpp>   //for ptr_push_back
#include <boost/tuple/tuple.hpp>
#include <boost/functional/hash.hpp>        //hash_combine函数
#include <string>
using namespace boost::multi_index;
using namespace std;

class person :
    boost::less_than_comparable<person>         //使用operators库实现全序比较
{
public:
    int             m_id;
    std::string     m_fname, m_lname;

    person(int id, const std::string& f, const std::string& l) :
        m_id(id), m_fname(f), m_lname(l) {}

    const std::string& first_name() const { return m_fname; }
    std::string& last_name() { return m_lname; }

    friend bool operator<(const person& l,const person& r)
    {
        return l.m_id < r.m_id;
    }

    friend bool operator==(const person& l,const person& r)
    {
        return l.m_id == r.m_id;
    }

    friend std::size_t hash_value(const person& p)
    {
        size_t seed = 2016;
        boost::hash_combine(seed, p.m_fname);
        boost::hash_combine(seed, p.m_lname);
        return seed;
    }
};

typedef composite_key<person*,      //元素类型为指针
    BOOST_MULTI_INDEX_MEMBER(person,int,m_id),
    BOOST_MULTI_INDEX_MEMBER(person,string,m_fname)
> comp_key;

int main()
{
    typedef multi_index_container<
        person*,                    //元素类型同样也是指针
        indexed_by<
            ordered_unique<comp_key>
        >
    > mic_t;

    //使用指针容器来存储元素,把多索引容器当作指针容器的一个视图来使用,避免手工删除指针的麻烦
    boost::ptr_vector<person> vec;
    using namespace boost::assign;
    ptr_push_back(vec)
        (2,"agent","smith")
        (1,"anderson","neo")
        (1,"the one","neo");            //id重复

    mic_t mic;
    for(auto& p : vec)      //插入指针元素到多索引容器
    {
        mic.insert(&p);
    }
    for(auto p : mic)       //顺序输出多索引容器内的元素
    {
        std::cout << p->m_id << ":" << p->m_fname << ",";
    }

    //find()和count()等涉及查找操作时需要使用tuple来构造查找值
    assert(mic.count(boost::make_tuple(1,"anderson")) == 1);
    assert(mic.find(boost::make_tuple(2,"agent")) != mic.end());

    //有序索引中可以仅顺序指定部分键值,这样索引将执行模糊查找,仅对指定的查找值执行比较
    assert(mic.count(boost::make_tuple(1)) == 2);   //仅指定一个键
    //使用第一个键,可以不使用tuple
    assert(mic.count(1) == 2);

    //对于散列索引不能指定部分键值,因为散列必须基于所有的键

    return 0;
}

If you want to perform customized work on composite keys, such as performing ascending order on one key and descending order on another key, the multi_index library provides multiple comparison predicates and hash function objects that operate composite_key_result.
Three basic function objects allow arbitrarily customized sorting criteria by combining comparison predicates or hash function objects based on individual keys.

template<typename Pred0,...,typename Predn>
struct composite_key_equal_to;

template<typename Compare0,...,typename Comparen>
struct composite_key_compare;

template<typename Hash0,...,typename Hashn>
struct composite_key_hash;

Assume that the m_id of the person class needs to be sorted in ascending order and the m_fname in descending order. The customized sorting criteria are as follows:

typedef composite_key_compare<
    std::less<int>,             //m_id升序
    std::greater<string>        //m_fname降序
> comp_key_compare;

When defining a multi-index container, you need to add the definition of the combined comparison predicate:

typedef multi_index_container<
        person*,    
        indexed_by<
            ordered_unique<
                comp_key,           //组合键
                comp_key_compare>   //组合比较谓词
        >
    > mic_t;

In order to facilitate the use of combined comparison predicates, the multi_index library defines four simplified function objects, using the standard library's equal_to, less, greater and boost.hash to operate all keys:

template<typename CompositeKeyResult>
struct composite_key_result_equal_to;

template<typename CompositeKeyResult>
struct composite_key_result_less;

template<typename CompositeKeyResult>
struct composite_key_result_greater;

template<typename CompositeKeyResult>
struct composite_key_result_hash;

If you want to sort both m_id and m_fname in descending order, you can define it directly as follows:

typedef multi_index_container<
        person*,    
        indexed_by<
            ordered_unique<
                comp_key,   
                composite_key_result_greater<comp_key::result_type>
            >
        >
    > mic_t;

Guess you like

Origin blog.csdn.net/zuolj/article/details/78890093