一、简介
引例
STL(standard template library)中的容器,如set, map,multimap, unordered_map等,可以存放key或key-value的数据, key是索引键,可以用来查找。
例如,为了记录员工信息:
struct employee
{
int id;
std::string name;
employee(int id,const std::string& name):id(id),name(name){}
bool operator<(const employee& e)const{return id<e.id;}
};
当我们既需要按id大小顺序访问,又需要按姓名的字母顺序访问,如果还用STL,就需要设计复杂的程序, 如:
- 用std::vector按id存放employee对象, 并在一个set中存放按name排序的employee的指针。
此时可以考虑使用Boost库中的multi_index_container多索引容器。
举例:
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/member.hpp>
using namespace ::boost;
using namespace ::boost::multi_index;
// define a multiply indexed set with indices by id and name
typedef multi_index_container<
employee,
indexed_by<
// sort by employee::operator<
ordered_unique<identity<employee> >,
// sort by less<string> on name
ordered_non_unique<member<employee,std::string,&employee::name> >
>
> employee_set;
void print_out_by_name(const employee_set& es)
{
// get a view to index #1 (name)
const employee_set::nth_index<1>::type& name_index=es.get<1>();
// use name_index as a regular std::set
std::copy(
name_index.begin(),name_index.end(),
std::ostream_iterator<employee>(std::cout));
}
从例子中可以看出定义multi_index_container要指定元素类型、索引类型。 上例中有两个随索引类型:ordered_unique<identity >和ordered_non_unique<member<employee,std::string,&employee::name> >。
索引
get()可以获取第N个索引, N从0到indexed_by<>里的index的个数。
不
过
,
m
u
l
t
i
_
i
n
d
e
x
_
c
o
n
t
a
i
n
e
r
也
可
以
直
接
访
问
,
这
是
时
候
默
认
是
通
过
第
0
个
索
引
访
问
的
\color{red}{不过,multi\_index\_container也可以直接访问,这是时候默认是通过第0个索引访问的}
不过,multi_index_container也可以直接访问,这是时候默认是通过第0个索引访问的
如下面这两种定义方法是等效的:
multi_index_container<(element)>
// is equialent to
multi_index_container<
(element),
indexed_by<
ordered_unique<identity<(element)> >
>
>
注 意 : g e t < N > ( ) 返 回 i n d e x 的 引 用 , 因 此 定 义 的 类 型 一 定 是 引 用 类 型 。 \color{red}{注意:get<N>()返回index的引用, 因此定义的类型一定是引用类型。} 注意:get<N>()返回index的引用,因此定义的类型一定是引用类型。
multi_index_container实例的索引项通过index_by指定。 如上文中ordered_unique, ordered_non_unique。 这里先支出,这两个索引类型需要元素提供比较谓词(comparison predicate )御用元素排序。
标签 tag
get中N为从0开始的索引号, 如果索引比较多, 记住number有点困难,还可以选择性的为为索引增加标签。例如
// tags
struct name{};
typedef multi_index_container<
employee,
indexed_by<
ordered_unique<identity<employee> >,
ordered_non_unique<tag<name>,member<employee,std::string,&employee::name> >
>
> employee_set;
typedef employee_set::index<name>::type employee_set_by_name;
employee_set_by_name::iterator it=es.get<name>().begin();
此时get()和get<1>()是等效的。
而且一个索引项,可以有多个标签, 如:
// tags
struct name{};
struct by_name{};
typedef multi_index_container<
...
ordered_non_unique<
tag<name,by_name>, // two tags
member<employee,std::string,&employee::name>
>
...
> employee_set;
迭代器访问
不同的索引项有不同的迭代器类型。 如:
typedef employee_set::nth_index<1>::type employee_set_by_name;
employee_set_by_name::iterator it=
es.get<1>().find("Judy Smith");
元素修改
所有索引的迭代器都是const的, 因此不能通过迭代器直接修改元素的值。 因为如果允许元素不加区别地修改,我们可以在不通知容器的情况下,在有序索引中引入不一致。
只能通过replace()或modify()的发方法修改元素。
二、索引
索引类型
目前Boost.MultiIndex提供以下索引类型:
ordered_unique and ordered_non_unique for ordered indices,
元素像std::set一样存放,关键字需要有operator< 用于排序,包括ordered_unique, ordered_non_unique。ranked_unique and ranked_non_unique for ranked indices,
和std::distance相关,没用过。hashed_unique and hashed_non_unique for hashed indices,
hashed_index是按hash值索引的,类似与std::unordered_map, 关键字需要有operator()或std::hash用于计算hash值, 同样有hashed_unique和hashed_non_unique两种sequenced for sequenced indices,
可以使容器元素像list一样顺序访问;random_access for random access indices.
支持随机访问,像vector一样可以用下标访问, 下标按插入的顺序递增。
带unique的是唯一索引,关键字不可重复; 带non_unique的是非唯一索引, 关键字可以重复, 下同。类似于std::set、std::multiset.
索引的定义规则为(以ordered**为例),
(ordered_unique | ordered_non_unique)
<[(tag)[,(key extractor)[,(comparison predicate)]]]>
关键值提取
索引的定义规则中的(key extractor),指定关键值提取。 可以是整个元素,如上例中的identity<employee>
, 返回employee对象, 也可以是元素的一个成员变量,如上例中的member<employee,std::string,&employee::name>
。
除了identify和member, 还有更多的关键值提取方法,以后介绍。
比较谓词
ordered**有序索引的定义规则最后的(comparison predicate) 制定了关键字的比较方法,默认是按std::less<key_type>排序的, 但是也可以自动以比较方法, 如:
// define a multiply indexed set with indices by id and by name
// in reverse alphabetical order
typedef multi_index_container<
employee,
indexed_by<
ordered_unique<identity<employee> >, // as usual
ordered_non_unique<
member<employee,std::string,&employee::name>,
std::greater<std::string> // default would be std::less<std::string>
>
>
> employee_set;
>
查找、检索
可以按照索引项的关键值查找, 如
employee_set es;
...
typedef employee_set::index<name>::type employee_set_by_name;
employee_set_by_name::iterator it=es.get<name>().find("Veronica Cruz");
对于有序索引, 还提供lower_bound,和upper_bound两个函数,用于范围查找,用法如下:
employee_set::iterator p0=es.lower_bound(employee(0,""));
employee_set::iterator p1=es.upper_bound(employee(100,""));
而且lower_bound和upper_bound参数甚至可以不是关键值类型。 employee_set的第#0个关键值是employee, 假设我们只想按id查找,可以如下:
struct comp_id
{
// compare an ID and an employee
bool operator()(int x,const employee& e2)const{return x<e2.id;}
// compare an employee and an ID
bool operator()(const employee& e1,int x)const{return e1.id<x;}
};
// and now write the search as
employee_set::iterator p0=es.lower_bound(0,comp_id());
employee_set::iterator p1=es.upper_bound(100,comp_id());
更新元素
因为作为索引的关键值的返回的元素是const的,不能直接通过=运算符修改容器元素, 只能通过以下方式(除非把某个没有作为任何索引关键值的元素成员变量,声明为mutable的, 则可以通过iterator直接修改该成员变量):
- replace(),替换
auto it=name_index.find("Anna Jones");
employee anna=*it;
anna.name="Anna Smith"; // she just got married to Calvin Smith
name_index.replace(it,anna); // update her record
- 替换操作如果不改变container的顺序,时间复杂度是O(1)的, 否则是O(logN)的。
- 替换后,迭代器仍有效
- 异常安全
- modify(), 修改
struct change_name
{
change_name(const std::string& new_name):new_name(new_name){}
void operator()(employee& e) // reference
{
e.name=new_name;
}
private:
std::string new_name;
};
...
auto it=name_index.find("Anna Jones");
name_index.modify(it,change_name("Anna Smith"));
此外还有类似的modify_key(), 它们都是异常不安全的。
顺序索引 Sequenced indices
类似与std::list, 使用如下:
multi_index_container<
int,
indexed_by<
sequenced<>, // sequenced type
ordered_unique<identity<int> > // another index
>
> s;
s.get<1>().insert(1); // insert 1 through index #1
s.get<1>().insert(0); // insert 0 through index #1
// list elements through sequenced index #0
std::copy(s.begin(),s.end(),std::ostream_iterator<int>(std::cout));
// result: 1 0
参考:
https://www.boost.org/doc/libs/1_62_0/libs/multi_index/doc/tutorial/index.html