游戏思考29:使用EASTL配合共享内存做自定义STL(未完待续12/28)

一、EASTL出现的契机(针对07年的STL)

1)简单介绍

跨平台健壮的容器库,拥有高性能的表现

2)EASTL的制作动机

标准STL一些设计和实现对于游戏开发者来说不是理想的实现,下面会列出原因:
1、STL的内存分配很容易导致代码膨胀和随之而来的低性能,原因传送门
2、STL的拓展容器例如slist、hash_map、shared_ptr等在STL不是那么便捷的使用,毕竟他们在不同版本的STL中是不一致的
3、STL缺少游戏开发者所需要的功能性,(eg.instrusive containers),和优化极佳的方便使用的STL环境
4、STL实现存在深度的函数调用,这会给在内联能力弱的编译器带来低效的性能,编译器还是档期啊流行的开源C++编译器,传送门1传送门2
5、STL很难调试
6、STL不能显示直接支持分配固定大小内存的容器空间
7、STL containers won’t let you insert an entry into a container without supplying an entry to copy from. This can be inefficient in the case of elements that are expensive to construct.
8、STL在大多数PC和box、TV游戏平台上有性能缺陷问题;很多实现没有具体的考虑到编译器和硬件的行为
9、同5,STL容器很难去追踪和调试,一些STL实现用的很隐晦的变量名字,文档也没有说明
10、STL容器隐秘的实现不让别人调整其数据结构
11、许多最近的STL的内存分配器使用不是最理想的方式导致一些容器内存的优化没法实现,这些实现能明显的提升性能。空容器不应该分配内存
12、所有现存的STL算法实现不能支持predicate的使用
13、除了在内存分配,STL重视正确性在实用性和性能之前

3)STL与EASTL的不同点

4)EASTL作用机理总结

5)游戏研发人员的问题

6)STL和EASTL的内存分配不同

7)EASTL的缺点

8)EASTL的容器介绍

二、STL前置知识学习(参考STL源码剖析)

1)萃取

(1)迭代器所指对象的类型-value_type

  • 备注
    因为迭代器需要知道所指向对象的类型
<1>第一个限制-返回参数需要指明迭代器的value_type

迭代器所指对象的类型,称为该迭代器的value type,上述的参数类型推导只可适用于推导函数参数,却不能作为函数的返回值

  • 解决办法
    声明内嵌类型
template <class T>
struct MysIter
{
    
    
	typedef T value_type;   //内嵌类型声明
	T*  Ptr;
	MyIter(T	* p=0):ptr(p){
    
    }
	T& operator*() const {
    
    return *ptr};
	//...
};

template <class T>
typename I::value_type; //这一整行是func的返回值类型
func(I ite)
{
    
    
	return *ite;
}


//...
MyIter<int> ite(new int(8));
cout<<func(ite); //输出8
  • 注意点
    func()的含绘制必须加上关键词typename,因为T是一个template参数,但它被编译器特例化之前,编译器对T一无所知。关键词typename的作用是告诉编译器这是一个类型,才能通过编译
<2>第二个限制坑点-不是所有迭代器都是class type,原生指针就不是

不是所有迭代器都是class type,原生指针就不是,可不是class type,就无法为他定义内嵌类型,但STL(及整个泛型思维)绝对必须接受原生指针作为一种迭代器,所以上面还不够。

  • 解决坑点
    方法:偏特化(template partial specialization)
  • 思想方法
    原生指针并非class,因此无法为他们定义内嵌类型,现在,可以针对迭代器template参数为指针设置特化版的迭代器了
template <class I>
struct iterator_traits  // traits 意为特性
{
    
    
	typedef typename I::value_type  value_type;	
};

这个所谓traits意义是:如果I定义有自己的value_type,那么通过这个traits的作用,萃取出来的value_type就是I::value_type,换句话说,如果I有自己的value_type,先前哪个func()可以改写成这样:(多了这一层,那么traits可以拥有特例化版本)

template <class I>
typename iterator_traits<I>::value_type  //这一样是返回值类型
func(I ite)
{
    
    
	return *ite;
}

现在做特例化版本如下:

template <class T>
struct iterator_traits<T*>  //偏特化版本,迭代器是原生指针
{
    
    
	typedef T value_type;
};
  • 结果
    于是,原生int*虽然不是一种class type,也可以通过traits取其value_type,这就解决了先前的问题
<3>第三个限制坑点-如果针对"指向常数对象的指针(point-to-const)"获得的值带const不是我们期望的
  • 举例
iterator_traits<const int*>::value_type  获得的是const int 而非int
  • 解决方法
    只需要设计一个特例化版本
template <class T>
struct iterator_traits<const T*>   //偏特化-当迭代器是point-to-const版本时,萃取出来T,
									//而非const T
{
    
    
	typedef T value_type;  
};

(2)最常用到的迭代器类型有五种:

在这里插入图片描述

<1>value_type
  • 概念介绍
    代指迭代器所指对象的类型
  • 意义
    任何一个跟stl完美搭配的class,都应该定义自己的value_type内嵌类型
<2>difference_type
  • 概念介绍
    表示两个迭代器之间的距离,因此它可以用来表示一个容器的最大容量,因为对于连续的容器而言,头尾之间的距离就是最大容量

  • 举例用法
    若一个泛型算法提供计数功能,例如STL的count(),其返回值就必须使用迭代器的difference_type

template<class I,class T>
typename iterator_traits<I>::difference_type //这一整行是返回值类型
count(I first,I last ,const T& value)
{
    
    
	typename iterator_traits<I>::difference_type n = 0;
	for(;first != last ;++first)
		if(*first == value)
			++n;
	return n;
}
  • difference_type针对原生指针的两种特化(以C++内建的ptrdiff_t作为原生指针的difference_type)
template<class T>
struct iterator_traits
{
    
    
	...
	typedef typename I::difference_type difference_type;
};

①针对原生指针设计的偏特化版本

template<class T>
struct iterator_traits<T*>
{
    
    
	...
	typedef ptrdiff_t difference_type;
};

②针对原生的pointer-to-const二设计的偏特化

template<class T>
struct iterator_traits<const T*>
{
    
    
	...
	typedef ptrdiff_t difference_type;
};
  • 总结
    现在当我们需要任何迭代器I的difference_type,可以这么写
typename iterator_traits<I>::difference_type;
<3>pointer_type
  • 作用
    传回一个pointer,指向迭代器所指之物
  • 举例
Item& operator*() const  {
    
    return *ptr;}  //这个就是reference_type
Item* operator->() const {
    
    return  ptr;}  //这个就是pointer_type
  • 把reference_type和pointer_type都加入到traits里面
template <class I>
struct iterator_traits
{
    
    
	...
	typedef typename I::pointer      pointer;
	typedef typename I::reference    reference;
};
  • 针对原生指针的两种特化
    ①针对原生指针的偏特化
template <class I>
struct iterator_traits<T*>
{
    
    
	...
	typedef typename T*      pointer;
	typedef typename T&      reference;
};

②针对原生的pointer-to-const的偏特化

template <class I>
struct iterator_traits<const T*>
{
    
    
	...
	typedef typename T*      pointer;
	typedef typename T&      reference;
};
<4>reference_type
  • 原理
    当我们要返回可修改的迭代器mutable iterator时,获得应该是一个左值,因为右值不允许赋值操作(assignment),左值才允许。
  • 解决方法
    C++中,函数要返回左值,都是以传引用的凡是。托value_type是T,那么*p的类型不该是T,而是T&;同理,若是constant iterator,*p的类型那就是const T&,而不是const T
<5>iterator_category
  • 迭代器分类(根据移动特性和操作)
    ①Input iterator: 只读的迭代器对象
    ②Output iterator: 只写的迭代器对象
    ③Forward iterator:在此迭代器上可进行读写操作
    ④Bidirectional iterator: 可双向移动
    ⑤Random Access iterator:前四种值提供一部分指针算数能力(举例:前三种支持operator++,第四种再加上operator–),第五种则覆盖所有指针算数能力,包括:p+n,p-n,p[n],p1-p2,p1<p2

  • 迭代器的概念和强化
    最高等级的迭代器类型并不代表最佳
    在这里插入图片描述

  • 举例说明最高等级的迭代器效率不一定最好(以advanced()为例子)
    ①该函数有两个参数,迭代器p和数值n,函数内部将p移动n位。下面有三分定义

1)针对Input Iterator
template<InputIterator, class Distance>
void advance_II(InputIterator& i, Distance n)
{
    
    
	//单向逐一前进
	while(n--)
		++i;
}
2)针对Bidirectional Iterator
template<BidirectionalIterator, class Distance>
void advance_BI(BidirectionalIterator& i, Distance n)
{
    
    
	//双向,逐一前进
	if(n >= 0)
		while(n--) ++i;
	else
		while(n++) --i;
}
3)针对Random Access Iterator
template<RandomAccessIterator, class Distance>
void advance_RAI(RandomAccessIterator& i, Distance n)
{
    
    
	//双向,跳跃前进
	i+=n;
}

现在当程序调用时,应该调用哪个调用呢?如果选择advance_II(),对RandomAccessIterator而言极度缺乏效率,例如返回上个迭代器,原本O(1)复杂度就需要O(N),如果选择advance_RAI(),则它无法接受Input iterator,如果将三者合一,则会如下显示:

template<class  InputIterator,class Distance>
void advance(InputIterator& i, Distanc n)
{
    
    
	if(is_random_access_iterator(i)) 
		advance_RAI(i,n);
	else if(is_bidirectional_iterator(i))
		advance_BI(i,n);
	else
		advance_II(i,n);
}

弊端:在执行期间才知道执行哪一个步骤,会影响程序效率。最好能在编译期间就做好,重载函数机制可以达到这个目标,下面定义五个class,代表五种迭代器类型:
在这里插入图片描述

  • 为什么使用重载
    不仅可以促成重载1机制的成功运作,使得编译器得以正确执行重载决议,另一个好处是通过继承,我们可以不必再写单纯制作传递调用的函数

这些class只在内部标记使用,所以不需要成员,所以原先的函数需要带上三个参数,做成内部函数__advance()
在这里插入图片描述
在这里插入图片描述

  • 最终结果
template<class InputIterator,class Distance>
inline void advance(InputIterator& i,Distance n)
{
    
    
	__advance(i,n,
		iterator_trais<InputIterator>::iterator_category());
}

注意:iterator_trais<InputIterator>::iterator_category()将会产生一个临时对象,类似int()产生一个int暂时对象一样,然后根据这个类型,编译器才会决定调用哪一个__advance()重载函数

  • 关于此行,SGI STL的源代码是:
__advance(i,n,iterator_category(i));
②并另定义函数iterator_category()如下:
template<class I>
inline typename iterator_traits<I>::iterator_categoy
iterator_category(const i&)
{
    
    
	typedef typename iterator_traits<I>::iterator_category category();
	return category();
}
③综合整理后原式子为
__advance(i,n,
	iterator_traits<InputIterator>::iterator_category());
  • 综合以上所述(为了增加STL效率),需要增加一个萃取类型
template<class I>
struct iterator_traits
{
    
    
	...
	typedef typename I::iterator_category  iterator_category;
};

①针对原生指针的偏特化版本
在这里插入图片描述

②针对原生指针的pointer-to-const而设计的偏特化版本
在这里插入图片描述

  • 备注
    任何一个迭代器,其类型永远应该落在”该迭代器所隶属各种类型最强化的哪个“,例如int* 既是RandomAccessIterator,又是BidirectionalIterator,同时也是ForwardIterator,而且也是Input Iterator。那么其类型归属应该是RandomAccessIterator

(3)STL算法命名规则

advanced()既可以接受各种类型迭代器,就不应该将其类型参数命名为InputIterator,其实是STL算法的一个命名规则,以算法所能接受值最低阶迭代器类型来为其迭代器类型参数命名

2)std::iterator的示例代码

(1)迭代器规范

为了符合STL规范,任何迭代器都应该提供五个内嵌类型,方便traits萃取,

(2)STL提供的模板参考

template<class Category,
		class  T,
		class  Distance  = ptrdiff_t,
		class  Pointer   = T*,
		class  Reference = T&>
struct iterator
{
    
    
	typedef Category     iterator_category;
	typedef T            value_type;
	typedef Distance     difference_type;
	typedef Pointer      pointer;
	typedef Reference    reference;
};

(3)总结:迭代器和容器、算法的责任区别

①迭代器的责任:设计适当的迭代器类型
②容器的责任:设计适当的迭代器(唯有容器才知道什么样的迭代器才适合自己,并执行怎样的操作,例如前进、后退、取值等)
③算法:独立于容器和迭代器之外自行发展,只要设计时以迭代器对外接口就行

3)SGI_STL的iterator源代码展示

  • 五种迭代器类型
struct input_iterator_tag{
    
    };
struct output_iterator_tag{
    
    };
struct forward_iterator_tag: public input_iterator_tag {
    
    };
struct bidiretional_iterator_tag:public forward_iterator_tag{
    
    };
struct random_access_iterator_tag:public bidiretional_iterator_tag {
    
    };
  • 迭代器构造(为适配STL规范,最好加上五个类型)
template<class Category,
		class  T,
		class  Distance  = ptrdiff_t,
		class  Pointer   = T*,
		class  Reference = T&>
struct iterator
{
    
    
	typedef Category     iterator_category;
	typedef T            value_type;
	typedef Distance     difference_type;
	typedef Pointer      pointer;
	typedef Reference    reference;
};
  • 萃取函数常用定义
template<class iterator>
struct iterator_traits
{
    
    
	typedef typename iterator::iterator_category         iterator_category;
	typedef typename iterator::value_type                value_type;
	typedef typename iterator::difference_type           difference_type;
	typedef typename iterator::pointer                   pointer;
	typedef typename iterator::reference                 reference;
};
  • 针对原生指针而设计的traits偏特化版
template<class T>
struct iterator_traits<T*>
{
    
    
	typedef typename random_access_iterator_tag          iterator_category;
	typedef typename T                                   value_type;
	typedef typename ptrdiff_t                           difference_type;
	typedef typename T*                                  pointer;
	typedef typename T&                                  reference;
};
  • 针对原生const 的原生指针设计的偏特化版本
template<class T>
struct iterator_traits<const T*>
{
    
    
	typedef typename random_access_iterator_tag          iterator_category;
	typedef typename T                                   value_type;
	typedef typename ptrdiff_t                           difference_type;
	typedef typename const T*                            pointer;
	typedef typename const T&                            reference;
};
  • 获取某个迭代器类型(category)的函数
template<class Iterator>
inline typename iterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&)
{
    
    
	typedef typename iterator_traits<Iterator>::iterator_category category;
	return category(); 
}
  • 很方便决定某个迭代器distance_type的函数
template<class Iterator>
inline typename iterator_traits<Iterator>::difference_type*
distance_type(const Iterator&)
{
    
    
	return static_cast<typename iterator_traits<Iterator>::distance_type*>(0);
}
  • 方便决定某个迭代器的value_type
template<class InputIterator>
inline iterator_traits<InputIterator>::value_type*
value_type(const Iterator&)
{
    
    
	return static_cast<typename iterator_traits<Iterator>::value_type*>(0);
}

4)SGI_STL私房菜:__type_traits

  • 备注
    ①__type_traits的前缀"__“表示SGI_STL内部使用的东西,不在STL标准规范之内
    ②iterator_traits负责萃取迭代器的特性,__type_traits负责萃取迭代器类型的特性(这里迭代器的类型特性是指:是否具备non-trival-defaltctor?是否具备non-trival copy ctor?等)若答案是否定的,我们再对这个类型进行构造、析构、拷贝、赋值等操作,就可以采用最有效率的措施(而非采用 根本不调用、不做事的构造函数),直接采用内存直接操作如malloc()\memcpy()等等,获得最高效率,这对于大规模且操作频繁的容器,有显著效率提升

  • __type_traits作用
    提供一种机制,允许针对不同的类型属性(type attributes),在编译时期完成函数派送决定(function dispatch)。

  • __type_traits作用机理
    当我们准备对”元素类型“未知的数组进行copy操作时,如果我们能事先知道其元素类型是否有一个trival copy constructor,便能够帮助我们决定是否可使用快速的memcpy()或memmove()

5)证同 identity

  • 概念
    任何数值通过此函数后,不会有任何改变
  • 作用
    ①证同函数用于<stl_set.h>,用来指定RB-tree所需的KeyOfValue op
    ②因为set原神的键值即为实际值,所以采用identity
template <class T>
struct identity:public unary_function<T,T>{
	const T& operator()(const T&x) const {return x;)
};

6)选择 select

7)投射 project

猜你喜欢

转载自blog.csdn.net/weixin_43679037/article/details/128445110