boost 多索引容器multi_index_container

6 篇文章 0 订阅

在游戏开发中遇到了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<member<Person, int, &Person::id> >,
		ordered_non_unique<member<Person, int, &Person::age> >,
		ordered_non_unique<member<Person, int, &Person::hei> >,
		ordered_non_unique<member<Person, std::string, &Person::name> >
	>
> 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<index>方法就可以得到这个索引排序的容器。例如,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<tag<person_id>, member<Person, int, &Person::id> >,
		ordered_non_unique<tag<person_age>, member<Person, Age, &Person::age> >,
		ordered_non_unique<tag<person_height>, member<Person, int, &Person::hei> >,
		ordered_non_unique<tag<person_name>, member<Person, std::string, &Person::name> >
	>
> PersonContainerTag;

容器插入的方式和上面一致,使用:pc2.get<person_name>()。

完整代码如下:

#include <string.h>
#include <stdio.h>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp> 
#include <boost/multi_index/ordered_index.hpp>

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<member<Person, int, &Person::id> >,
		ordered_non_unique<member<Person, Age, &Person::age> >,
		ordered_non_unique<member<Person, int, &Person::hei> >,
		ordered_non_unique<member<Person, std::string, &Person::name> >
	>
> 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<tag<person_id>, member<Person, int, &Person::id> >,
		ordered_non_unique<tag<person_age>, member<Person, Age, &Person::age> >,
		ordered_non_unique<tag<person_height>, member<Person, int, &Person::hei> >,
		ordered_non_unique<tag<person_name>, member<Person, std::string, &Person::name> >
	>
> 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<person_name>();
	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<boost::multi_index::detail::bidir_node_iterator<boost::multi_index::detail::ordered_index_node<boost::multi_index::detail::ordered_index_node<boost::multi_index::detail::ordered_index_node<boost::multi_index::detail::index_node_base<Person, std::allocator<Person> > > > > >, 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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【为什么还需要学习C++?】 你是否接触很多语言,但从来没有了解过编程语言的本质?你是否想成为一名资深开发人员,想开发别人做不了的高性能程序?你是否经常想要窥探大型企业级开发工程的思路,但苦于没有基础只能望洋兴叹? 那么C++就是你个人能力提升,职业之路进阶的不二之选。【课程特色】 1.课程共19大章节,239课时内容,涵盖数据结构、函数、类、指针、标准库全部知识体系。2.带你从知识与思想的层面从0构建C++知识框架,分析大型项目实践思路,为你打下坚实的基础。3.李宁老师结合4大国外顶级C++著作的精华为大家推出的《征服C++11》课程。【学完后我将达到什么水平?】 1.对C++的各个知识能够熟练配置、开发、部署;2.吊打一切关于C++的笔试面试题;3.面向物联网的“嵌入式”和面向大型化的“分布式”开发,掌握职业钥匙,把握行业先机。【面向人群】 1.希望一站式快速入门的C++初学者; 2.希望快速学习 C++、掌握编程要义、修炼内功的开发者; 3.有志于挑战更高级的开发项目,成为资深开发的工程师。 【课程设计】 本课程包含3大模块基础篇本篇主要讲解c++的基础概念,包含数据类型、运算符等基本语法,数组、指针、字符串等基本词法,循环、函数、类等基本句法等。进阶篇本篇主要讲解编程中常用的一些技能,包含类的高级技术、类的继承、编译链接和命名空间等。提升篇:本篇可以帮助学员更加高效的进行c++开发,其中包含类型转换、文件操作、异常处理、代码重用等内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值