boost 多索引容器multi_index_container

在游戏开发中遇到了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方法就可以得到这个索引排序的容器。例如,pc.get<0>()返回的就是以id字段索引的容器,pc.get<1>()返回的是以age字段为索引的容器。注意这里的index是定义容器时的顺序,而不是结构体中字段定义的顺序。

那么以某个索引的顺序是正序还是逆序呢,这个和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

你可能感兴趣的:(C++,算法)