在游戏开发中遇到了boost的multi_index_container,中文翻译为多索引容器。那什么是多索引容器呢?为什么使用他?如何使用他?下面就一一介绍。
想必大家在实际开发中一定多多少少会遇到以下的问题,我需要创建一个map,并且需要两种方式去索引,比如:创建一个<学号,姓名>的map,但是我既需要用学号去索引,又需要用姓名去索引,但std::map只能用它的key_type(在这里是学号)作为索引进行查找操作,该怎么办呢?“这有何难”?有人可能想到了,用value_type(姓名)索引也是可以的嘛,大不了写一个函数去遍历这个map,然后把要查找的姓名跟每个pair的second比较一下,如果相等不就找到了么?OK,没错,这样可以解决问题,但仅仅只是很粗糙地解决问题,如果这个map很大,每次查找都要遍历一遍,查找平均复杂度为O(n/2),如果你在需要高效的商业项目上使用,相信到性能测试的那天就是你收拾包袱走人的时刻。
让我们来讨论一些高效点的方法吧,仍然使用标准库的std::map,这次我们创建两个,一个使用<学号,姓名>,另一个则是<姓名,学号>,在每加入一个学生的时候都需要在两个map中各插入一个元素,移除的时候亦是,这种方法比最初的方法要好一些了,至少它可以做到高效的双向查找,但是仍然还是有缺陷的,比如说维护起来很麻烦,每次操作时都需要同时关照两个map的对应关系,一旦忽略了一些细节导致其中一个出了一点差错,可能就会酿成大错。
对于这种简单的需要双向查找的容器,使用boost::bimap就可以方便的解决问题,boost::bimap就是专为这种情况设计的容器,当然它的强大可能超出了你的想象,但是这里我们讨论的不是它,我们在实际开发中遇到的情况往往更复杂,比如说要创建一个<学号,学生信息>(学生信息是一个结构)的map,用前面的方法就得稍稍麻烦一点,比如重载学生信息结构的operator==来进行索引的依据,这些吃亏不讨好的方法我在这里就不再重复讨论了,让我们进入本章的正题,使用multi_index_container,没错,它可以轻松的帮你解决如上的所有问题。
让我们先创建一个简单的结构,来说明Person:
struct Person {
int id;
int age;
int hei;
std::string name;
Person(int i, int a, int h, std::string n) {
id = i;
age = a;
hei = h;
name = n;
}
void Print() const {
printf("name : %s, id is %d, age is %d, hei is %d\n", name.c_str(), id, age, hei);
}
};
然后定义一个可以有多个索引的容器:
typedef multi_index_container <
Person,
indexed_by <
ordered_unique >,
ordered_non_unique >,
ordered_non_unique >,
ordered_non_unique >
>
> PersonContainer;`
该容器定义了四种索引,其中有的字段值是唯一的,例如实际中id号不可以重复,而其他的都存在相同的情况。
容器的插入和stl容器的插入类似,例如:
PersonContainer pc;
pc.insert(Person(2, 15, 170, "azhang"));
pc.insert(Person(1, 17, 167, "dzhang"));
pc.insert(Person(3, 18, 173, "czhang"));
pc.insert(Person(0, 16, 167, "bzhang"));
然后如何按照某个字段索引呢?很简单,直接调用容器的get
那么以某个索引的顺序是正序还是逆序呢,这个和stl中map的规则一致,基本类型就是按默认的从小到大的顺序,结构体则是要重载<运算符。
以定义字段的顺序有个弊端就是不太好记忆,而且随着代码更改也会发送变化。boost想到了这一点,提供了以名字为索引的方式:
struct person_id{};
struct person_age{};
struct person_height{};
struct person_name{};
typedef multi_index_container <
Person, indexed_by<
ordered_unique, member >,
ordered_non_unique, member >,
ordered_non_unique, member >,
ordered_non_unique, member >
>
> PersonContainerTag;
容器插入的方式和上面一致,使用:pc2.get
完整代码如下:
#include
#include
#include
#include
#include
using boost::multi_index_container;
using boost::multi_index::ordered_unique;
using boost::multi_index::ordered_non_unique;
using boost::multi_index::indexed_by;
using boost::multi_index::member;
using boost::multi_index::tag;
struct Age { //为了演示排序的方式
Age(int a) :age(a) {}
int age;
bool operator<(const Age& a)const{ return age > a.age; }
};
struct Person {
int id;
Age age;
int hei;
std::string name;
Person(int i, Age a, int h, std::string n) :age(a) {
id = i;
hei = h;
name = n;
}
void Print(const char* order) const {
printf("order: %s, name is %s, id is %d, age is %d, hei is %d\n", order, name.c_str(), id, age.age, hei);
}
};
typedef multi_index_container <
Person,
indexed_by <
ordered_unique >,
ordered_non_unique >,
ordered_non_unique >,
ordered_non_unique >
>
> PersonContainer;
typedef PersonContainer::nth_index<0>::type IdIndex;
typedef PersonContainer::nth_index<1>::type AgeIndex;
typedef PersonContainer::nth_index<3>::type NameIndex;
struct person_id{};
struct person_age{};
struct person_height{};
struct person_name{};
typedef multi_index_container <
Person, indexed_by<
ordered_unique, member >,
ordered_non_unique, member >,
ordered_non_unique, member >,
ordered_non_unique, member >
>
> PersonContainerTag;
int main()
{
PersonContainer pc;
pc.insert(Person(2, Age(15), 170, "azhang"));
pc.insert(Person(1, 17, 167, "dzhang"));
pc.insert(Person(3, 18, 173, "czhang"));
pc.insert(Person(0, 16, 167, "bzhang"));
IdIndex& ids = pc.get<0>(); //其拷贝构造函数是被保护的,所以只能返回引用,为该字段的容器
for (auto it = ids.begin(); it != ids.end(); ++it) {
it->Print("id");
}
printf("\n\n");
AgeIndex& ages = pc.get<1>();
for (auto it = ages.begin(); it != ages.end(); ++it) {
it->Print("age");
}
printf("\n\n");
NameIndex& names = pc.get<3>();
for (auto it = names.begin(); it != names.end(); ++it) {
it->Print("name");
}
PersonContainerTag pc2;
pc2.insert(Person(2, Age(15), 170, "azhang"));
pc2.insert(Person(1, 17, 167, "dzhang"));
pc2.insert(Person(3, 18, 173, "czhang"));
pc2.insert(Person(0, 16, 167, "bzhang"));
auto& names2 = pc2.get();
for (auto it = names2.begin(); it != names2.end(); ++it) {
it->Print("names2");
}
return 0;
}
还有另外的插入方式:
AgeIndex& ages = pc.get<1>();
auto p = ages.insert(Person(3, 19, 171, "ezhang")).first;
注意,这里p的值不一定是Person(3, 19, 171, "ezhang")哦,取决于插入的是否允许重复。这里id字段不能重复,而3又重复了,所以插入失败,返回原来id为3的元素。
插入之后的类型是stl::pair对:
std::pair > > > > >, bool>
第一眼除了感觉C++真他妈复杂之外,还能有什么其他想法?
第二个参数,表明是不是插入正确,如果字段不能重复,但插入了重复的字段,bool就为false,返回的p为该字段的对象。
参考:
https://blog.csdn.net/liuqiuyuewo/article/details/75332345
https://blog.csdn.net/lsjseu/article/details/43370707
https://blog.csdn.net/lee353086/article/details/40706669