多索引容器

《C++11/14高级编程:Boost程序库探秘》笔记

多索引容器,顾名思义,提供多种对元素的访问方式。
在有的时候,可能会想对同一组元素执行不同的访问顺序准则,比如既要以顺序方式遍历list里的元素,又想以大小排序的方式遍历,又或者想针对元素的某个属性查找。指针容器和侵入式容器在一定程度上可以解决,指针容器可以使用视图分配器对另一个容器建立视图,从而在不改变原容器的情况下提供新的访问方式;侵入式容器直接修改元素的结构,可以添加任意多个挂钩,每一个挂钩对应一种访问接口。
但是局限性在于:指针容器要求元素必须是专有的,有时需要满足克隆概念;而侵入式容器必须修改元素的定义,很多时候不被允许。
boost.multi_index库解决了这类问题,它提供了多索引容器,能够以不同的索引方式访问同一组存储在容器中的元素。


入门示例
简单例子
#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;
}
复杂例子
#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;
}

索引是一个元数据,它需要使用一种被称为键提取器(key extractor)的函数对象来获取用于产生索引的键类型,这个例子中,直接使用元素本身作为键,所以使用identity。
如果不显式指定索引,那么容器将使用第一个索引,如果想要获得第一个以外的索引,就要使用模板成员函数get<N>(),它返回一个特定的索引类型的引用:mic_t::nth_index::type&,通常情况下,可以直接使用auto/decltype简化声明:

auto& hash_index = mic.get<1>();    //correct
auto  hash_index = mic.get<1>();    //error,不能声明索引实例变量
更复杂的例子
#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;
}

该例子中,容器的第一个索引是sequenced,并不对元素排序,所以通常不需要模板参数;第二个索引是ordered_unique,有序单键索引,要求元素具有operator<;第三个索引是ordered_non_unique,使用一个不同于identity的新的键提取器member,形式上类似侵入式容器的member_hook,可以使用元素的成员变量作为键;第四个索引是hash_unique,无序单键索引,要求元素满足boost.hash可以计算散列值。


multi_index库里的基本概念
1.索引

索引是multi_index库中最重要的概念,它是多索引容器里访问元素的接口,决定了外部用户以何种准则对元素执行读/写/查找等操作。不同的索引有不同的使用接口,但接口的定义都完全模仿标准容器,具有大多数常用的成员函数,所以在使用时完全可以把它当成标准容器。
索引通常不允许直接修改元素,因为一个索引容器可能持有多个索引,相互之间有制约关系,某些操作无法执行。
虽然索引是确定的类型,但是它被定义为一个内部使用的类型,我们无法自行声明一个索引类型的变量,索引的使用必须依附于多索引容器,在multi_index_container的模板参数中用索引说明定义,然后用模板成员函数get<N>()来获得引用才能操作。

2.索引说明

索引说明是一个高阶元数据,它使用键提取器和其他参数来定义索引,内部有两个名为node_class和index_class的元函数,返回可供容器使用的节点类型和索引类型。
目前multi_index库提供四类索引说明:

  • 序列索引:这类索引只有一个sequenced,类似std::list的双向链表序列访问接口。
  • 随机访问索引:这类索引只有一个random_access,类似std::vector的序列访问接口,提供operator[]方式的随机访问能力。
  • 有序索引:包括ordered_unique和ordered_non_unique,类似std::set的有序集合访问接口。
  • 散列(无序)索引:包括hashed_unique和hashed_non_unique,类似std::unordered_set的无序集合访问接口。
3.键提取器

键提取器是一个单参函数对象,它可以从元素或元素的boost.ref包装中获取用作索引(排序)的键,通常用作索引说明的第一个模板参数。
multi_index库提供六种键提取器:

  • identity:使用元素本身作为键
  • member:使用元素的某个public成员变量作为键
  • const_mem_fun:使用元素的某个const成员函数的返回值作为键
  • mem_fun:使用元素的某个非const成员函数的返回值作为键
  • global_fun:使用操作元素的某个全局函数或静态成员函数的返回值作为键
  • composite_key:可以把以上的键提取器组合为一个新的键

键提取器获取的键类型必须能够满足索引的要求,例如有序索引要求键定义了比较操作符,散列索引要求键可以计算散列值和执行相等比较。

4.索引说明列表

索引说明列表是多索引容器multi_index_container的第二个模板参数,实现为一个indexed_by结构,用来定义多索引容器使用的索引。
indexed_by基于模板元编程库mpl里的元数据序列mpl::vector,是一个类型的容器,可以容纳多个索引说明。它使用了预处理元编程,当前实现限定最多容纳20个元数据,也就是说最多在一个容器上同时使用20个索引。

5.索引标签

get<N>()通过序号来获取索引,但是这样不方便不好记忆,所以multi_index库提供索引标签(tag)的概念,允许使用语法标签来访问索引。
tag是一个mpl类型容器,最多支持20个类型同时作为索引标签,标签类型是任意定义的,还可以使用C++内建类型int、std::string。但是一个多索引容器中不允许使用重复的标签。

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

auto& ordered_index = mic.get<id>();
auto& hashed_index = mic.get<int>();
6.多索引容器

多索引容器的类型是multi_index_container,声明是:

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

类模板参数分别是元素类型,排序准则和分配器类型,只不过排序准则非常复杂,是一个indexed_by类型的索引声明列表,索引说明列表缺省提供一个ordered_unique,意味如果不指定索引说明,多索引容器默认行为同std::set,是一个不允许重复的有序集合。


键提取器
1.定义

键提取器是一个函数对象,基本形式如下:

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;         //操作链式指针类型
};

键提取器的operator()不仅可以操作类型本身(T&),也可以操作被boost.ref库包装的对象(reference_wrapper<T>&),而且它还支持”链式指针“(chained pointer)对象。
所谓”链式指针“是指”链式指针“一系列类似于指针对象的组合——包括原始指针、智能指针、迭代器等一切具有operator*可以执行解引用操作的类型,T*、T**、shared_ptr<T*>都属于链式指针类型,它们可以被连续递归解引用得到一个非指针类型T&或reference_wrapper<T>&。这个特性可以轻松操作指针,让多索引容器可以轻松地容纳指针类型的元素。
multi_index库中所有预定义的键提取器均位于头文件<boost/multi_index/key_extractors.hpp>,想要减少编译时间,可以视情况包含必要的头文件。

2.identity

identity是一个最简单的键提取器,直接使用元素本身作为键,不做任何提取动作,相当于标准容器的键类型,只要元素类型不是const,那么就是可写的键提取器。
identity位于头文件<boost/multi_index/identity.hpp>,类摘要如下:

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

使用元函数if_c,根据类型Type是否被const修饰分别交给const_identity_base和non_const_identity_base处理。
const_identity_base主要代码如下:

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

前两个operator()都直接返回变量自身,最后一个重载形式用于处理链式指针,递归生成解引用的operator(),这样运行时可以连续调用直至获取最终的Type&类型。
identity其实就是一个普通的函数对象,例如:

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有些类似非标准函数对象select1st的功能,可以提取类型里的某个public成员变量作为键。接口实现与identity基本相同,也使用了元函数if_c,根据类型Type是否被const修饰分别交给const_member_base和non_const_member_base处理,只要元素类型不是const,那么它就是可写的键提取器。
member有三个模板参数,在形式上很像侵入式容器的member_hook选项,指定要提取的类型Class、键类型Type(即类型的成员变量类型)以及成员变量指针PtrToMember,例如:

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

某些对C++标准支持较差的编译器可能无法使用member,multi_index提供了一个等价替代品:member_offset,使用偏移量来代替成员指针,为了获得最大兼容性且方便使用,multi_index定义了一个宏BOOST_MULTI_INDEX_MEMBER,无须手工写出成员变量指针的声明,根据编译器的功能自动选用member或者member_offset。

4.const_mem_fun

const_mem_fun使用类型中的某个const成员函数的返回值作为键,有些类似函数对象mem_fn,但它有两个限制:只能调用const成员函数,而且这个成员函数必须是无参调用。
const_mem_fun是一个只读键提取器,位于头文件<boost/multi_index/mem_fun.hpp>,类摘要如下:

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()定义
};

它的模板参数和member类似,但最后一个参数是成员函数指针,同时Class和Type参数必须与成员函数指针精确匹配,比如:

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也有一个用于屏蔽编译器差异的宏BOOST_MULTI_INDEX_CONST_MEM_FUN。

5.mem_fun

mem_fun与const_mem_fun类似,只是使用的是元素的某个非const成员函数的返回值作为键,它是一个只读键提取器,头文件位置与const_mem_fun一样。
需要注意的是mem_fun与标准库里的函数对象std::mem_fun重名,使用时注意加名字空间boost::multi_index限定。也可以使用宏BOOST_MULTI_INDEX_MEM_FUN,这样不必考虑名字空间的问题。

6.global_fun

global_fun使用一个全局函数或静态成员函数来操作元素,将函数的返回值作为键,它类似于identity,支持const或非const的函数。
global_fun是一个只读键提取器,位于头文件<boost/multi_index/global_fun.hpp>,类摘要如下:

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

用法类似于const_mem_fun和mem_fun,只是最后一个模板参数必须是一个参数为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.自定义键提取器

键提取器实质上是一个单参函数对象,所以也可以不使用multi_index库预定义的键提取器,完全自行编写,只要满足键提取器的定义。
自定义键提取器首先要满足标准函数对象的要求,定义包含内部类型result_type,它同时也是键类型。然后要实现操作元素类型的operator(),必须是const成员函数,根据需要可实现数个针对T&、reference_wrapper<T>&和ChainedPtr&的重载,但不必都实现。
例如,可以编写一个键提取器person_name,实现与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;
    }
}

序列索引

序列索引是最简单的一种索引,实际上没有对元素做任何索引操作,仅仅是顺序存储元素。序列索引位于头文件<boost/multi_index/sequenced_index.hpp>
序列索引的索引说明是sequenced,它的类摘要如下:

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

因为序列索引不基于键排序,所以sequenced不使用任何键提取器,只有一个TagList模板参数,用来给索引贴语法标签。
序列索引使用的类detail::sequenced_index提供类似于std::list的双向链表操作,但有些接口是常量性的,不能随意修改元素。

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()操作

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

sequence_index的接口与list基本相同,主要注意几点:

  • 索引不提供public的构造函数和析够函数(由多索引容器处理),但可以使用operator=和assign()赋值,用法同标准容器
  • 解引用迭代器(begin()、rbegin()等)返回的都是const类型,不能用迭代器修改元素
  • 访问元素的front()和back()函数返回的是const引用,不能修改元素
  • 因为可能存在其他索引约束,push_front()、push_back()和insert()都可能失败,所以返回值同set::insert()一样的一个pair,second成员表示操作是否成功
  • 与侵入式容器类似,索引提供iterator_to()函数,可以从容器内一个元素的引用获取相应的迭代器。

用法:

#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));
}
随机访问索引

随机访问索引是另一种序列索引,同样是顺序存储元素,但提供了比序列索引更多的访问接口。
随机访问索引位于头文件<boost/multi_index/random_access_index.cpp>
它的索引说明是random_access,与sequenced非常相似,同样不使用键提取器,只有一个标签模板参数。
随机访问索引使用的类是detail::random_access_index,它是sequenced_index的超集,拥有sequenced_index的全部功能,多出了两个可以随机访问任意位置元素的operator[]和at(),其他接口相同,但操作的时间复杂度不同。
random_access_index在使用上虽然很像std::vector,但是本质上还是链式容器,不像std::vector那样提供连续的元素存储。
用法:

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

虽然random_access_index提供随机访问元素的功能,但因为它的接口都是常量性的,很多需要使用迭代器赋值操作的标准算法(如排序算法、替换算法)都无法使用:

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

有序索引基于键提取器对元素进行排序,使用红黑树结构提供类似于set的有序集合访问接口。位于头文件<boost/multi_index/ordered_index.hpp>
有序索引的索引说明包括ordered_unique和ordered_non_unique,前者不允许重复键,后者允许重复,两者声明和接口均相同,以ordered_unique为例,类摘要如下:

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

它有三个模板参数,因为使用了模板元编程技术,所以最少提供一个模板参数就可以工作。

  • 第一个参数可以是标签或键提取器,必须提供
  • 如果第一个是标签,那么第二个参数必须是键提取器
  • 最后一个参数是比较谓词对象,缺省是std::less<typename KeyFromValue::result_type>,即对键提取器的小于比较

有序索引使用的类是detail::ordered_index,接口类似于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;//参数为两个函数对象
};

用法和std::set没有太多区别,注意不能使用迭代器修改元素。

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

有序索引有两种高技用法,兼容键比较和获得范围区间,以person类为例。
兼容键比较,所谓兼容键是指不同于索引说明中键本身的一个类型,但它的比较效果与键相同。例如person类中,identity<person>定义的键类型是person,但它的比较操作符operator<使用的是类型为int的m_id成员变量,所以int是它的兼容键,而很显然构造一个int类型的成本比构造一个person类型的成本低很多,效率自然会提升。
为了使用兼容键比较函数,需要为person类定义一个与int类型比较的谓词:

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

这样在使用时就不必使用整个元素来执行比较操作了:

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

获取范围区间使用了成员函数range(),它是一个模板函数,参数(LowerBounder和UpperBounder)是两个以键为参数的谓词函数或函数对象,用于确定区间的下界和上界,相当于a < x && x < b,比直接使用lower_bound()和upper_bound()方便直观。
例如,为person的id定义一个2 <= p.m_id < 20 左闭右开区间,lower_bound()和upper_bound()用法如下:

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

这种写法很容易迷惑,不好把握端点条件,使用有序索引的range()函数就简单得多,上下界函数对象可以清晰地实现:

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

使用C++11/14的lambda表达式可以简化上面的繁琐的上下界谓词编写,生成匿名函数对象:

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

有序索引还定义了一个特别的谓词函数unbounded,可以用在区间的任意一个端点,表示该端点无限制:

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

散列索引基于键提取器(元素的)键进行散列,提供类似std::unordered_set的无序集合接口。散列索引位于头文件<boost/multi_index/hashed_index.hpp>
散列索引的索引说明包括hashed_unique和hashed_non_unique,前者不允许重复键而后者允许重复键,两者的声明和接口均相同,以hashed_unique为例:

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

它提供四个模板参数,使用了与ordered_unique相同的模板元编程技术,最少提供一个模板参数就可以工作。

  • 第一个参数可以使标签或键提取器,必须提供
  • 如果第一个是标签,那么第二个参数必须是键提取器
  • 键提取器后的参数是散列函数对象,缺省是boost::hash<typename KeyFromValue::result_type>
  • 最后一个参数是相等比较谓词对象,缺省是std::equal_to<typename KeyFromValue::result_type>

散列索引使用的类是hashed_index,接口类似于std::unordered_set,类摘要如下:

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

hashed_index与unordered_set的接口类似,因为是无序的,所以不提供成员函数lower_bound()、upper_bound()和range()。此外hashed_index还有一些与散列容器相关的特殊成员函数,如桶数量,负载因子等。
用法上散列索引就像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;
}
修改元素

multi_inde库中所有索引的迭代器接口都是常量性的,不允许用户直接修改,因为一个变动操作可能会导致其他索引不一致,为此multi_index使用了另外的机制,所有的索引都提供两个专门用于修改元素的成员函数:replace()和modify(),有序索引和散列索引还提供了一个特别的修改键的成员函数modify_key(),这些操可以保持多索引容器的状态不被破坏。
替换元素
成员函数replace()可以替换一个有效迭代器位置上的元素的值,其他索引保持同步更新。
通常可以使用find()算法或者find()成员函数获取迭代器位置,再调用bool replace(iterator position,const value_type&x); 替换元素。如果使用iterator_to()则需要小心,使用元素的等价拷贝获得的是一个无效迭代器,用于replace()会产生运行异常。
示例:

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

修改元素
replace()成员函数虽然提供了强异常安全保证,但在操作时必须构造一个完整的临时元素对象,操作成本较高。modify()是另一种修改元素的方法,它使用一个被称为修改器的函数或函数对象,接受一个元素的引用作为参数,可以只变动元素的某个成分,是轻量级的修改元素方法。
modify的声明如下:

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

它有两个参数,第一个是要修改的迭代器位置,与replace()相同,第二个是修改器对象,执行元素的修改操作。
例如,我们想要修改person的各个成员变量,使用C++11/14的lambda表达式可以在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");

modify()虽然避免了构造临时对象的成本,执行效率高,但是它不能保证操作的安全,如果元素修改后与索引的约束发生冲突,那么修改将失败,而元素会被删除!

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

为了解决这种现象,modify()提供了一种类似于数据库回滚机制的重载形式,允许用户使用一个“回滚”函数或函数对象在修改失败时恢复原有的值,这种形式的modify()函数原型如下:

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

用法如下:

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_key(),可以直接修改索引使用的键而不是元素本身。使用modify_key()要求索引的键提取器必须是可写的。
modify_key的声明如下:

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

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

由于modify_key()的修改器操作的不是元素本身的类型,而是键类型,所以编写lamba表达式时参数要使用键类型:

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

多索引容器

前面介绍了键提取器和索引,本节介绍真正的多索引容器。
前面列出了多索引容器的前置声明,它的类摘要:

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

本质上multi_index_container是一个索引的容器,它本身只负责管理索引,各个索引负责元素的管理。
get<N>()是最重要的成员函数,有两种重载形式,分别可以使用整数序号或者类型标签来获取索引,因此索引也有两种类型,分别是nth_index<N>和index<tag>,这两个实际上是元函数,需要使用::type的形式获得真正的索引类型,通常使用时用auto/decltype来简单避免类型声明的问题。
project<N>()用来在多个不同索引之间转换的迭代器,可以把一个索引的迭代器投射到另一个索引的迭代器中,而两个迭代器所指向的是同一个元素。
multi_index_container是可序列化的,如果不需要,可以定义宏BOOST_MULTI_INDEX_DISABLE_SERIALIZATION,禁用序列化代码,加快编译速度。
示例一个八个索引的多索引容器用法:

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

组合索引键

类似于数据库里联合主键概念,有时候仅用一个键对元素排序可能不够,需要基于多个键查找元素,multi_index库提供了组合索引键composite_key,可以把多个键提取器组合为一个新的键供索引使用,位于头文件<boost/multi_index/composite_key.hpp>
composite_key是一个只读提取键,基于boost.tuple实现对多个键提取器的组合,它的第一个模板参数是元素类型Value,可以跟着一个或多个组合的键提取器。这些提取器可以是identity、member、const_mem_fun等,但必须也以Value为元素类型,目前最多支持组合十个键提取器。
composite_key的operator()返回类型是composite_key_result,是一个简单的struct,重载了标准比较谓词equal_to、less、greater和boost的hash操作,并且支持与tuple的比较操作,可以像基本类型一样使用。
用法:
composite_key可以用于有序索引和散列索引,比如使用composite_key定义一个基于m_id和m_fname的组合索引键,注意元素类型使用指针,意味着要在容器中存储指针类型:

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

如果想要对组合键进行定制工作,比如对一个键执行升序而另一个键执行降序,multi_index库为此提供了多个操作composite_key_result的比较谓词和散列函数对象。
三个基本的函数对象可以通过组合基于单个键的比较谓词或散列函数对象来任意定制排序准则。

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;

假设需要对person类的m_id升序而对m_fname降序,定制排序准则如下:

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

在定义多索引容器时需要增加组合比较谓词的定义:

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

为了方便组合比较谓词的使用,multi_index库定义了四个简化的函数对象,使用标准库的equal_to、less、greater和boost.hash操作所有键:

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;

如果要对m_id和m_fname都进行降序排序,可以直接如下定义:

typedef multi_index_container<
        person*,    
        indexed_by<
            ordered_unique<
                comp_key,   
                composite_key_result_greater<comp_key::result_type>
            >
        >
    > mic_t;
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值