SGI stl
第一章
1.9 令人困惑的语法
1.9.1 stl_config.h中的各种组态(configurations)
- 组态3:__STL_STATIC_TEMPLATE_MEMBER_BUG:函数模板中包含静态数据成员
- 组态5:__STL_CLASS_PARTIAL_SPECIALIZATION:类模板的一般化设计之外,针对某些template参数做特殊化设计,如const,指针类型等
- 组态6:__STL_FUNCTION_TMPL_PARTIAL_ORDER:函数模板部分参数具体化
- 组态7:__STL_EXPLICIT_FUNCTION_TMPL_ARGS:SGI STL中没有用到这一常量定义
- 组态8:__STL_MEMBER_TEMPLATES:类模板中包含模板成员(参数不同于类模板参数)
- 组态10:__STL_LIMITED_DEFAULT_TEMPLATES:template参数根据前一个tempplate参数设定默认值,后者包含前者;
- 组态11:_STL_NON_TYPE_TMPL_PARAM_BUG:class template包含non-typetemplate参数
-
组态12:__STL_NULL_TMPL_ARGS( bound friend template friend):class的某一个具体化与其friend function template的某个具体化保持一对一的关系
image.png -
组态13:__STL_TEMPLATE_NULL:类模板显式具体化
image.png
1.9.2 临时对象的产生与运用
- 在类型名后面直接加一个小括号构造临时对象如int(10),此种做法在stl里面多被用于functor和算法的搭配上:使用临时对象作为一个functor实现算法的某一个逻辑体,并自动回收
template<class T>
class print{
public:
void operator()(const T& elem){ cout<<elem<<' ';}
};
int main(){
int ia[6]={0,1,2,3,4,5};
vector<int> iv(ia,ia+6);
//临时对象
for_each(iv.begin(),iv.end(),print<int>());
}
1.9.3 静态常量整数成员在class内部直接初始化
- 所有整形(int、char、long等)静态常量都能在类中设初值(声明)
1.9.4 ++/--/*操作符
- 这三种操作符在迭代器的设计上占有十分重要的地位,根据不同类型的迭代器给定不同的操作子集
1.9.5 前闭后开区间
- 任何一个算法都需要获得一个前闭后开的操作范围:比如
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f){
for(;first!=last;++first)
f(*first)
return f;
}
1.9.6 function call操作符(operator())
- 对于一个算法我们可以使用不同的策略(function),而c语言中这是通过函数指针实现的,但是它无法持有自己的状态,也无法达到组件技术的可适配性,函数的静态的,无法添加修饰条件。
- stl里面就用仿函数(functor):针对某一个class做operator()重载
- 单纯的只进行重载还不够(没有可接配的能力),我们需要进一步修饰提高
2 空间配置器
注意:这里的空间配置器包含内存和其他存储介质
2.1空间配置器的标准接口
数据成员:
- allocator::value_type
- allocator::pointer
- allocator::const_pointer
- allocator::reference
- allocator::const_reference
- allocator::size_type
- allocator::difference_type
成员函数
- allocator::rebind 嵌套模板类拥有唯一成员other
- allocator::allocator()
- allocator::allocator(const allocator&)
- template <class U> allocator::allocator(const allocator<U>&)
- allocator::~allocator()
- pointer allocator::address(reference x)const
- const_pointer allocator::address(const_reference x)const
- void allocator::allocate(size_type n, const void*=0)
- size_type allocator::max_size()const
- void allocator::destroy(pointer p)
2.2 具备次配置力的SGI空间配置器
SGI STL的配置器是alloc而不是allocator,不接受任何参数。
2.2.1 SGI标准的空间配置器
- std::allocator是SGI自带一个符合部分标准的配置器,但是因为性能不佳,只对operator new和delete做了浅层的封装,所以不建议使用。
2.2.2 SGI特殊的空间配置器(使用)std::alloc
-
精细的将alloc分为两阶段。并分别提供接口,alloc和construct,包含在两个头文件里面
image.png
2.2.3 构造和析构工具:construct()和destroy()
image.png- 上述过程就是构造和析构的过程,对于destroy()而言做了更加细致的分类,在范围删除时,需要利用value_type()和_type_traits<T>判断对象的析构函数是不是trivial,如果是啥也不干;对于construct,主要是用placement new在已分配的空间上创建对象并初始化。
c++本身不支持对“指针所指之物”的类型判断,也不支持“对象的析构函数是否trivial”的判断,所以上述的两个接口很值得考究
2.2.4 空间的配置与释放 std::alloc
- 设计哲学:向system heap要空间;考虑多线程;内存不足的应变;fragmentation
- SGI为应对小型区块的内存碎片问题,设计了双层级配置器,第一级使用malloc()和free(),第二级因时制宜:当区块超过128B使用一级;反之为降低overhead,采用memory pool整理模式,此时就不依赖一级模式了。使用_USE_MALLOC(#ifdef)是否定义判断二级配置器是否打开。
-
为了适配标准,对alloc做了一层封装simple_alloc,这个才是stl容器真正用的接口
image.png -
接口架构
image.png
2.2.5 第一级配置器__malloc_alloc_template剖析
- 第一级配置器使用malloc、free、realloc等c函数实现内存操作,模拟new机制,但是并不是直接使用new机制,::operator new并没有被启用。new-handler机制解决内存不足的做法有特定的模式(取决于客端),详情请参考E7.
new-handler机制:
- 让更多内存可以被使用
- 安装另一个new-handler
- 卸除new-handler
- 抛出bad_alloc
- 不返回,调用abort或者exit
2.2.6 第二级配置器__default_alloc_template
image.png- 为了避免额外负担所占的比例过大(也就是区块过小,如上图),二级配置器使用内存池的做法:每一次配置一大块内存用于维护一个freelists链表,客户端的分配与回收都在这个分配器层面上进行。16个内存对象链表各包含8的整数倍大小的区块。free-lists节点结构如下
union obj{
union obj* free_list_link;
char client_data[1];//the client sees this
}
- 使用union可以让指针和数据复用同一块内存区块,这种技巧在强类型语言中行不通,这种技巧的实质内涵是:当内存区块空闲时,我们用其保存指针变量,当区块被返回给用户时,区块的地址被用户态维护,区块改为存储数据。即在用户态和内核态分别维护不同的内容
- 将bytes上调至8的倍数
(((bytes)+7)&~7)
- 将bytes计算空闲链表索引值
(((bytes)+7)/7)
2.2.7 空间配置器
static void* allocate(size_t n){
obj* volatile* my_free_list;
obj* result;
if(n>(size_t)__MAX_BYTES){
return(malloc_alloc::allocate(n));//一级配置选项
}
my_free_list=free_list+FREELIST_INDEX(n);
result=*my_free_list;
if(result==0){
void *r=refill(ROUND_UP(n));
return r;
}
*my_free_list=result->free_list_link;
return(result);
}
static void deallocate(void* p,size_t n){
obj* q=(obj*)p;
obj* volatile *my_free_list;
if(n>(size_t)__MAX_BYTES){
malloc_alloc::deallocate(p,n);
return;
}
my_free_list=free_list+FREELIST_INDEX(n);
q->free_list_link=*my_free_list;
*my_free_list=q;
}
2.2.8 内存补充
template<bool threads,int inst>
void* __default_alloc_template<threads,inst>::refill(size_t n){
int nobjs=20;//一次性分配20个对象的内存
char* chunk=chunk_alloc(size_t n,nobjs);
obj* volatile* my_free_list;
obj* result;
obj* current_obj,*next_obj;
int i;
if(1==nobjs) return (chunk);
my_free_list=free_list+FREELIST_INDEX(n);
result=(obj*)chunk;
//将开辟的内存分成大小相等的对象然后串起来
*my_free_list=next_obj=(obj*)(chunk+n);
for(i=1;;i++){
current_obj=next_obj;
next_obj=(obj*)((char*)next_obj+n);//注意转型
if(nobjs-1==i){
current_obj->free_list_link=0;
break;
}else{
current_obj->free_link_list=next_obj;
}
}
return (result);
}
2.2.9 内存池
template<bool threads,int inst>
char*
__default_alloc_template<threads,inst>::
chunk_alloc(size_t size,int& nobjs){//对象大小、对象个数
char* result;
size_t total_bytes=size*nobjs;
size_t bytes_left=end_free-start_free;//left memory
if(bytes_left>=total_bytes){//内存池足够分配20个对象
result=start_free;
start_free+=total_bytes;
return (result);
}else if(bytes_left>=size){//内存池足够分配一个以上的对象
nobjs=bytes_left/size;
total_bytes=size*nobjs;
result=start_free;
start_free+=total_bytes;
return (result);
}else{//内存池资源不足
size_t bytes_to_get=2*total_bytes+ROUND_UP(heap_size>>4);//新水量的大小为需求的两倍再加上一个随着配置次数增加愈来愈大的附加量
if(bytes_left>0){
obj* volatile* my_free_list=free_list+FREELIST_INDEX(bytes_left);
((obj*)start_free)->free_list_link=*my_free_list;
*my_free_list=(obj*)start_free;
}
start_free=(char*)malloc(bytes_to_get);
if(0==start_free){
int i;
obj *volatile* my_free_list,*p;
//系统堆资源不够,需要从freelists较大的对象中检索,看有没有内存可用 for(i=size;i<=__MAX_BYTES;i+=__ALIGN){
my_free_list=free_list+FREELIST_INDEX(i);
p=*my_free_list;
if(0!=p){
*my_free_list=p->free_list_link;
start_free=(char*)p;
end_free=start_free+i;
return (chunk_alloc(size,nobjs))
}
}
end_free=0;
//调用第一级配置器,第一级处理器有out-of-memory处理机制
start_free=(char*)malloc_alloc::allocate(bytes_to_get);
}
heap_size+=bytes_to_get;
end_free=start_free_bytes_to_get;
return (chunk_alloc(size,obj));
}
}
2.3 内存基本处理工具
stl定义了5个全局函数作用于未初始化的空间上,前两个对应于构造和析构函数、另外三个函数unintialized_copy()、uninitialized_fill()、uninitialized_fill_n(()分别对应于高层次函数copy()、fill()、fill_n().
2.3.1 函数语义简介
- 该函数能够将内存的配置和对象的构造分离开来。该函数对于给定的输入范围,使用construct(&(result+(i-first)),i)将其对象一个一个的拷贝进输出空间中
- 容器的全区间构造函数通常以两个步骤完成:
- 配置内存区块,足以包含范围内的所有元素;
- 使用这个函数构造元素。
- 该函数满足“commit or rollback”语义。
- uninitialized_fill、uninitiated_fill_n类似
2.3.2 函数实现
\\uninitialized_fill_n
template <class ForwardIterator,class Size,class T>
inline ForwordIterator uninitialized_fill_n(ForwordIterator first,size n,const T&x){
return __uninitialized_fill_n(first,n,x,value_type(first));
}
template <class ForwardIterator,class Size,class T,class T1>
inline ForwardIterator __uninitialized_fill_n(ForwardIterator first,Size n,const T&x,T1*){
typedef typename __type_traits<T1>::is_POD_type is_POD;
return __uninitialized_fill_n_aux(first,n,x,is_POD());
}
template <class ForwardIterator,class Size,class T>
inline ForwardIterator __uninitialized_fill_n_aux(ForwardIterator first,Size n,const T& x,__true_type){
return fill_n(first,n,x);//高阶库,这个是处理c struct以及简单数据类型用的,也就是POD数据类型
}
template <class ForwardIterator,class Size,class T>
ForwardIterator __uninitialized_fill_n_aux(ForwardIterator first, Size n, const T & x, __false_type){
ForwardIterator cur=first;
for(;n>0;--n,++cur)
construct(&*cur,x);
return cur;
}
\\uninitialized_copy和uninitialized_fill;
\\大部分和上述做法一样,不过处理char*和wchar_t*两种类型时,采用了最具效率的memmove内存直接存取
\\注意返回的是end
\\memmove的特殊性在于其可以用来处理处理内存重叠的问题 ,而memcpy不可以\ \ end \ \ mem void * memmove(void * dest, const void * src, size_t count)
void *memmove(void *dest,const void *src,size_t count)
{
char *ret=(char *)dest;
char *dest_t=dest;
char *src_t=(char *)src;
assert( NULL !=src && NULL !=dest);
//或者改为dest-src>=count
if (dest_t<=src_t || dest_t>=src_t+count)
{
while(count--)
{
*dest_t++ = *src_t++;
}
}
else
{
dest_t+=count-1;
src_t+=count-1;
while(count--)
{
*dest_t--=*src_t--;
}
}
return ret;
}
\\“新的指针指向已经分配的内存”就会出现内存重叠问题,有时候这会出现
第三章 迭代器的概念与traits的编程技巧
3.1 迭代器设计思维——stl关键所在
迭代器模式定义:提供一种方法,使之能够寻访某个聚合物所含的各个元素,而又无需暴露该聚合物的内部表达式。在stl中,它将容器和算法分开,彼此独立设计,容器和算法的泛型化并不难,关键是如何设计良好的胶着剂,也就是设计出可以泛用而又能反映容器自身特性的迭代器,不能过于依赖,又不能过于平庸。
3.2 迭代器是一种smart pointer
- 迭代器是一种行为类似指针的对象,其中dereference和member access的实现是最重要的工作
- 从smart pointer上获取灵感,然后作泛化处理
3.3 迭代器相应类型
- 算法使用迭代器时很可能会用到迭代器所指之物的类型,然而只有sizeof而没有typeof,typeid()获得的类型名也不能做变量声明。解决办法是:使用函数模板的参数推导机制(调用函数时自动根据参数推导参数类型,这样模板被隐式具体化后,就可以使用了)。但是这不能解决5种型别问题,返回值无法推导
- 思维进化:指针-->智能指针-->嵌套类型声明-->偏特化机制
Traits编程技巧—STL源代码门钥
- 嵌套类型声明举例:
template <class T>
struct MyIter{
typedef T value_type;
T* ptr;
MyIter(T* p=nullptr):ptr(p){}
T& operator*()const{
return *ptr;
}
}
tempplate<class I>
typename I::value_type func(I ite){
return *ite;
}
存在问题:原生指针不是class type,所以无法定义其内嵌型
- partial specilaization的意义:针对template参数更进一步的条件限制所设计出来的一个特化版本
template<typename T> classC<T*>{};
这里就是一个要求“T为原生指针”的特化模板 - 下面的class template就是用来萃取迭代器的特性,而value type正是迭代器的特性之一:
template<class I>
struct iterator_traits{
typedef typename I::value_type value_type;
}
template <class I>
typename iterator_traits<I>::value_type
func(I ite){return *ite;}
//进一步
template<class I>
struct iterator_traits<I*>{
typedef I value_type;//特化为原生指针
}
//引用
iterator_traits<const int*>::value_type
//进一步:从pointer-to-const中萃取出non-const
template<class T>
struct iterator_traits<const T*>{
typedef T value_type;
}
- 要这个特性萃取机能够有效运作,每一个迭代器都必须遵守约定,自行以内嵌类型定义value_type。这是一个约定,谁不遵守这个约定,谁就不能兼容于STL这个大家庭
-
五种迭代器型别都需要被定义
image.png
template<class T>
struct iterator_traits{
typedef typename I::iterator_category iterator_category;
typedef typename I::value_type value_type; //迭代器所指对象的类型
typedef typename I::difference_type difference_type;//迭代器之间的距离
typedef typename I::pointer pointer;//
typedef typename I::reference reference;
};
//设计举例,对于每一个iterator都应该设置它的萃取机,迭代器类型为I,对于原生指针需要做一些偏特化
//以difference_type型别,count算法为例
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;
}
//以referenc_type和pointer_type这两个的完整设计为例
template <class I>
struct iterator_traits{//普通iterator版本
...
typedef typename I::pointer pointer;
typedef typename I::reference reference;
...
};
template<class T>
struct iterator_traits<T*>{//原生指针的偏特化
...
typedef T* pointer;
typedef T& reference;
...
};
struct iterator_traits<const T*>{
...
typedef const T* pointer;
typedef const T& referencde;
...
};
- const本身可以作为一种偏特化的指标,对于返回值的精细控制有重要意义
-
iterator_category
image.png
设计算法时,如果可能,尽量针对上图中的每一中迭代器提供一个明确定义。而在给容器设定迭代器时尽量选择功能上的最佳匹配。
3.5 std::iterator的保证
为了符合规范,任何迭代器都应该提供五个内嵌相应型别。STL提供一个iterators class如下,如果每个设计的迭代器都继承自它:
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;
};
//iterator class不含任何成员,所以继承不会带来额外的开销
template<class Item>
struct ListIter: public std::iterator<std::forward_iterator_tag,Item>
{...}
- 总结:设计适当的型别是迭代器的责任,设计适当的迭代器是容器的责任。只有容器本身知道该设计出怎样的迭代器来遍历自己,并执行迭代器该有的各种行为。至于算法,完全可以独立于容器和迭代器之外自行发展,只要设计时以迭代器为对外接口即可;traits使用内嵌型别和编译器的template参数推导功能增强c++未能提供的关于型别认证方面的能力,弥补c++不为增强型语言的遗憾
3.6 iterator源代码完整重列:
//<stl_iterator.h>
struct input_iterator_tag {};
struct forward_iterator_tag:public input_iterator_tag{};
struct bidirectional_iterator_tag:public forward_iterator_tag{};
struct random_access_iterator_tag:public bidirectional_iterator_tag{};
//自己开发的迭代器应该继承这个类
template <class Category,class T,class Distance=ptrdiff_t,class Pointer=T*,class Reference=T&>
struct iterator{
...
}
//traits
template<class Iterator>
struct iterator_traits{
...
内嵌声明
}
//原生指针的traits
template<class T>
struct iterator_traits<T*>{
...
}
//pointer-to-cosnt
template<class T>
struct iterator_traits<const T*>{
...
}
//函数,决定迭代器的类型
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函数
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
__distance(InputIterator first,InputIterator last,input_iterator_tag){
iterator_traits<InputIterator>::difference_type n=0;
while(first!=last){
++first;
++n;
}
return n;
}
template <class RandomAcessIterator>
inline iterator_traits<RandomAcessIterator>::difference_type
__distance(RandomAcessIterator first,RandomAcessIterator last,random_access_iterator_tag){
return last-first;
}
//distance(InputIterator first,InputIterator last){... return __distance(first,last,category());}
//可见最后一个参数是用来重载的
//advance函数
3.7 SGI STL的私房菜:__type_traits
traits被进一步运用在迭代器以外的世界里面,于是就有了所谓的__type_traits。
双底线前缀词意指这是SGI STL内部所用的东西,不在STL之内
iterator_traits负责萃取迭代器的特性,__type_traits则负责萃取型别的特性。此时我们关注的型别特性是指是否具备non-trivial default ctor、copy ctor、assignment operator、dtor?如果答案是否定的,我们在对这个型别执行上述操作时就可以采用最有效率的措施,如malloc、memmove等。对于大规模而操作频繁的容器有着显著的效率提升
- 定义于SGI <type_traits.h>中的__type_traits,提供了一种机制,允许针对不同的型别属性,在编译时完成函数派送决定。
__type_traits<T>::has_trivial_default_constructor
__type_traits<T>::has_trivial_copy_constructor
__type_traits<T>::has_trivial_assignment_operator
__type_traits<T>::has_trivial_destructor
__type_traits<T>::is_POD_type
//使用真假性质的对象来标志他们
struct __true_type{};
struct __false_type{};
//template <class type>
struct __type_traits{
typedef __true_type this_dummy_member_must_be_first;
typedef __false_type ...;
...
- SGI保守的把所有内嵌型别都定义为__false_type(采用繁琐的处理方式), 然后针对每一个标量型别设计适当 的特化版本
- 5个typedefs将经由以下管道获得实值
- 一般具体化,保守值
- 经过声明的特化版本
- 某些编译器会自动为所有型别提供适当的特化版本
- non-trivial的一个简单的判断标准:如果class内含有指针成员,并且对它进行内存动态配置,那么这个class就需要实现自己的non-trivial--xxx。
4 容器
image.png4.1 vector
所谓序列式容器,其中元素可序,但是未必有序。
4.1.1 vector概述
- array是静态空间,编译时确定空间大小,vector是动态配置内存
- vector的实现技术关键是对其大小的控制以及重新配置时数据移动效率的问题。
4.1.2 vector定义摘要
template <class T,class Alloc=alloc>
class vector{
public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type* iterator;
typedef value_type& reference;
typedef size_t size_type;
typedef ptrdiff_t diffrence_type;
protected:
typedef simple_alloc<vlaue_type,Alloc> data_allocator;
iterator start;
iterator finish;
iterator end_of_storage;
void insert_aux(iterator position,const T& x);
void deallocate(){
if(start)
data_allocator::deallocate(start,end_of_storage-start);
}
void fill_initialize(size_type n,const T& value){
start=allocate_and_fill(n,value);
finish=start+n;
end_of_storage=finish;
}
public:
iterator begin(){return start;}
iterator end(){return finish;}
size_type size()const {
return size_type(end()-begin());
}
size_type capacity()const {
return size_type(end_of_storage-begin());
}
bool empty()const {
return begin()==end();
}
reference operator[](size_type n){
return *(begin()+n);
}
vector():start(0),finish(0),end_of_storage(0){}
vector(size_type n,const T& value){
fill_initialisze(n,value);
}
vector(long n,const T& value){
fill_initialisze(n,value);
}
explicit vector(size_type n){
fill_initialize(n,T());
}
~vector(){
destroy(start,finish);
deallocate();
}
reference front(){return *begin();}
reference back(){return *(end()-1);}
void push_back(){
if(finish!=end_of_storage){
construct(finish,x);
finish++;
} else{
insert_aux(end(),x);
}
}
void pop_back();
iterator erase(iterator position){
if(position+1!=end())
copy(position+1,finish,position);
--finish;
destroy(finish);
return position;
}
void resize(size_type new_size,const T& x){
if(new_size<size())
erase(begin()+new_size(),end());
else
insert(end(),new_size-size(),x);
}
void resize(size_type new_size){
resize(new_size,T());
}
void clear(){
erase(begin(),end());
}
protected:
iterator allocate_and_fill(size_type n,const T&x){
uninitialized_fill_n(result,n,x);
return result;
}
4.1.3 vector的迭代器
- vector的迭代器是全能的,所以其迭代器其实就是int*
-
数据结构:线性连续空间,用start和finish分别指向配置来的连续空间中目前已被使用的范围,并以end_of_storage指向整块连续空间的尾端
image.png
4.1.4 vector的构造与内存管理:constructor,push_back
- 所谓动态增加大小,并不是在原空间之后接续新空间(无法保证原空间后尚有可配置的空间),而是以原大小的两倍另外配置一块较大空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原始空间。因此,对于vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。这是程序员易犯的错误。
4.1.5 vector的元素操作:pop_back(),erase,clear,insert
iterator erase(iterator first,iterator last){
iterator i=copy(last,finish,first);
destroy(i,finish);
finish=finish-(last-first);
return first;
}
iterator erase(iterator position){
if(position+1!=end())
copy(position+1,finish,position);//,这是一个全局函数,为什么不能直接用最后一个元素与当前元素交换?为了保护顺序性
}
insert操作
image.png
image.png
代码
template<class T,class Alloc>
void vector<T,Alloc>::insert(iterator position,size_type n,const T& x)
{
if(n!=0){
if(size_type(end_of_storage-finish)>=n){
T x_copy=x;
const size_type elems_after=finish-position;
iterator old_finish=finish;
if(elems_after>n){
uninitialized_copy(finish-n,finish,finish);
finish+=n;
copy_backward(position,old_finish-n,old_finish);
fill(position,position+n,x_copy);
}else{
uninitialized_fill_n(finish,n-elems_after,x_copy);
finish+=elems-after;
uninitialized_copy(position,old_finish,finish);
finish+=elems_after;
fill(position,old_finish,x_copy);
}
}else{
const size_type old_size=size();
const size_type len=old_size+max(old_size,n);
iterator new_start=data_allocator::allocate(len);
iterator new_finish=new_start;
__STL_TRY{
new_finish=uninitialized_copy(start,position,new_start);
new_finish=uninitialized_copy(position,finish,new_finish);
}
#ifdef __STL_USE_EXCEPTIONS
catch(...){
destroy(new_start,new_finish);
data_allocator::deallocate(new_start,len);
throw;
}
#endif
destroy(new_start,new_finish);
deallocate();
start=new_start;
finish=new_finish;
end_of_storage=new_start+len;
}
}
}
4.2 list概述
4.2.1 list概述
- 相较于vector的连续线性空间,list就显得复杂很多,它的好处就是每一次插入和删除一个元素就配置一个或者删除一个元素。因此,list对于空间的运用有绝对的精准,一点也不浪费。
- list和vector是两种最常用的容器,怎么选择依赖于元素的多寡、元素的构造复杂度、元素存取行为的特性等而定
4.2.2 list的节点
- list本身和list的节点是不同的结构
template<class T>
struct __list_node{
typedef void* void_pointer;
void_pointer prev;
void_pointer next;
T data;
};
4.2.3 list的迭代器
- list不能够像vector一样以普通指针作为迭代器,因为其节点不保证在存储空间中连续存在。list迭代器必须有能力指向list的节点,并有能力正确的递增、取值、递减、成员存取等操作。所以list提供的是Bidrectional iterators
- list有一个重要性质:插入操作和接合操作都不会造成原有list的迭代器失效。
4.2.4 list的数据结构
- list不仅是一个双向链表,而且还是一个环状双向链表。list维护一个尾部的空白成员node,其next节点为begin节点
4.2.5 list的构造和内存管理constructor、push_back、insert
- insert
iterator insert(iterator position, const T& x){
//插入的元素是构造在当前位置的前面
tmp->next=position.node;
tmp->prev=position.node->prev;
(link_type(position.node->prev))->next=tmp;
position.node->prev=tmp;
return tmp;
}
插入完成之后新节点位于哨兵迭代器所指节点的前方,这是对于插入操作的标准规范
- list内部提供一个迁移操作:将某个连续范围的元素迁移到某个特定位置之前。技术上很简单,节点间的指针移动而已。这个操作为其他的复杂操作如splice、sort、merge等奠定了良好的基础。下面是transfer的源代码
void transfer(iterator position, iterator first,iterator last){
if(position!=last){
(*link_type((*last.node).prev))).next=position.node;
(*link_type((*first.node).prev))).next=last.node;
link_type tmp=link_type((*position.node).prev);
(*position.node).prev=(*last.node).prev;
(*first.node).prev=tmp;
}
}
- 上述transfer并非公共接口。tranfer接口适用于同一个list的不同区段的迁移也适用于不同list之间的迁移。list公开提供的所谓接合操作splice:将某个连续范围的元素从一个list移动到另一个list的某个定点。以下提供三个版本的splice接口:
- void splice(iterator,list&);
- void splice(iterator,list&,iterator);
- void splice(iterator,list&,iterator,iterator);
- 其他接口:
template<class T,class Alloc>
void list<T,Alloc>::merge(list<T,Alloc>&x){
iterator first1=begin();
iterator last1=end();
iterator first2=x.begin();
iterator last2=x.end();
while(first1!=last1&&frist2!=last2){
if(*first2<*first1){
iterator next=first2;
transfer(first1,first2,++next);
first2=next;
}else{
++first1;
}
if(first2!=last2)
transfer(last1,first2,last2);
}
}
template<class T,class Alloc>
void list<T,Alloc>::reverse(){
if(node->next==node||link_type(node->next)->next==node)
return;
iterator first=begin();
++first;
while(first!=end()){
iterator old=first;
++first;
transfer(begin(),old,first);
}
}
//本函数采用归并排序,为了避免每次寻找分割点,使用中间链表来保存有序元素,其中每一个链表保存归并相应下标层数的所有有序元素,有效的实现用空间换时间的效果
template<class T,class Alloc>
void list<T,Alloc>::sort(){
if(node->next==node||link_type(node->next)->next==node){
return;
}
list<T,Alloc> carry;
list<T,Alloc> counter[64];//用于存放中间元素,存放元素的数量和下标有关
int fill=0;
while(!empty()){
carry.splice(carry.begin(),*this,begin()));//每一次取一个元素
int i=0;
//一步一步合并直至合并完成
while(i<fill&&!counter[i].empty()){
//必须递增排序
counter[i].merge(carry);//counter用于存储归
carry.swap(counter[i++]);//完全置换,置换过后,counter[i]为空,carry为前n个元素
}
carry.swap(counter[i]);//这一步过后,carry为空,counter[fill]保存有序元素
if(i==fill) ++fill;
}
for(int i=1;i<fill;++i){
counter[i].merge(counter[i-1]);
}
swap(counter[fill-1]);
}
list不能使用STL算法的sort,必须使用自己的member function,因为STL只接受RandomAccessIterator
注意这里的sort的实现,使用归并的方法实现排序,由于swap是直接操作指针,不存在复制元素的代价,所以时间复杂度为O(1),算法总的时间复杂度为O(nlogn)。该算法是链表排序的一个经典实现,思想是使用二进制进位
4.3 deque
4.3.1 deque的概述
- vector是单向开口的连续线性空间,deque是双向的(循环队列)。
个人理解:这里的连续指的是逻辑连续,即可以使用random access iterator
- deque与vector的差异,一在于deque允许在常数时间内在端头进行插入和删除,二是deque没有容量的概念。
deque是动态的组织分段连续空间,随时可以增加一段新的空间并链接起来,所以不会像vector那样更新复制原始空间。
references:
{STL源码剖析简体中文完整版](https://www.linuxidc.com/Linux/2016-04/129761.htm)
- deque的迭代器提供random access iterator的功能,但是他不是普通指针,其复杂度较高,所以除非必要,我们应尽可能使用vector。对deque的排序操作,应先将其复制到vector,排好序后再复制回。
4.3.2 deque的中控器(map)
- deque旨在在分段连续空间上维护其整体连续的假象,而其代价就在于随机迭代器的连续架构设计
- deque使用map(一个二维指针)作为主控,具体代码和架构如下
template<class T, class Alloc=alloc,size_t BufSiz=0>
class deque{
public:
typedef T value_type'
typedef value_type* pointer;
...
protected:
typedef pointer* map_pointer;
protected:
map_pointer map;
size_type map_size;
...
}
4.3.3 deque的迭代器
- “整体连续”的任务主要交给迭代器的operator++和operator--。
- deque迭代器的结构
template <class T, class Ref,class Ptr, size_t BufSiz>
struct __deque_iterator{
typedef __deque_iterator<T,T&,T*,BufSiz> iterator;
typedef __deque_iterator<T,const T&,const T*,BufSiz> const_iterator;
static size_t buffer_size(){ return __deque_buf_size(BufSiz,sizeof(T));}
//未继承自std::iterator,所以必须撰写五个必要的迭代器型别
typedef random_access_iterator_tag iterator_category;//(1)
typedef T value_type;//(2)
typedef Ptr pointer;//(3);
typedef Ref reference;//(4);
typedef size_t size_type;
typedef ptrdiff_t difference_type;//(5)
typedef __deque_iterator self;
//保持与容器的连接
T* cur;//当前元素
T* first;//迭代器所指缓冲区的头
T* last;//迭代器所指缓冲区的尾,注意这里和vector的last是一样的,都是最后一个空元素
map_pointer node;//指向管控中心中当前缓冲区的地址存放的地方
}
- 迭代器的几个关键行为
缓冲区的跳转
void set_node(map_pointer new_node){
node=new_node;
first=*new_node;
last=first+difference_type(buffer_size());
}
各种运算子重载
reference operator*() const{return *cur;}
pointer operator->() const{return &(operator*());}
difference_type operator-(const self& x)const {
return difference_type(buffer_size())*(node-x.node-1)+(cur-first)+(x.last-x.cur);
}
self& operator++(){
++cur;
if(cur==last){
set_node(node+1);
cur=first;
}
return *this;
}
self operator++(int){
self tmp=*this;
++*this;
return tmp;
}
self& operator--(){
if(cur==first){
set_node(node-1);
cur=last;
}
--cur;
return *this;
}
self& operator+=(difference_type n){
difference_type offset=n+(cur-first);
if(offset>=0&&offset<difference_type(buffer_size()))
cur+=n;
else{
difference_type node_offset=offset>0?offset/difference_type(buffer_size()):-difference_type((-offset-1)/buffer_size())-1;
set_node(node+node_offset);
cur=first+(offset-node_offset*difference_type(buffer_size()));
}
return *this;
}
self operator+(difference_type n) const{
self tmp=*this;
return tmp+=n;
}
self& operator-=(difference_type n){return *this+=-n;}
self operator-(difference_type n) const{
self tmp=*this;
return tmp-=n;
}
reference operator[](difference_type n) const{return *(*this+n);}
bool operator==(const self& x)const {return cur==x.cur;}
bool operator!=(const self& x) const{return !(*this==x);}
bool operator<(const self& x) const{
return (node==x.node)?(cur<x.cur):(node<x.node);
}
4.3.4 deque的数据结构
- deque还维护start和finis两个迭代器,分别指向第一个缓冲区的第一个元素和最后一个缓冲区的最后一个元素的下一个空位置,同时记录map本身的大小,便于扩展当前主控缓冲区。
4.3.5 deque的构造和内存管理
- deque自定义了两个空间配置器
typedef simple_alloc<value_type,Alloc> data_allocator;
typedef simple_alloc<pointer,Alloc> map_allocator
- 一个构造器
deque(int n, const value_type& value): start(), finish(), map(0), map_size(0)
{
fill_initialize(n, value);
}
// the definition of fill_initialize
template<class T, class Alloc, size_t BufSiz>
void deque<T, Alloc, BufSiz>::fill_initialize(size_type n, const value_type& value){
create_map_and_nodes(n);
map_pointer cur;
__STL_TRY{
for(cur=start.node;cur<finish.node;++cur)
uninitialized_fill(*cur,*cur+buffer_size(),value);
uninitialized_fill(finish.first, finish.cur, value);
}
catch(...){
...
}
start.set_node(nstart);
finish.set_node(nfinish);
start.cur=start.first;
finish.cur=finish.first+num_elements%buffer_size();
}
- 对于push_back
如果最后一个缓冲区有备用空间,则直接插入即可;否则开辟一个新的缓冲区 - 对于push_front
如果第一个缓冲区之前还有备用空间,直接插入;否则开辟一个缓冲区 - 重置map
调用reserve_map_at_back, 如果尾端备用空间不足(标准是node_to_add+1>map_size-(finish.node-map))),则必须使用reallocate配置一个更大的,并拷贝map;
调用reserve_map_at_front,同理。
4.3.6 deque的元素操作
- pop_back(pop_front同理)
if(finish.cur!=finish.first){
--finish.cur;
destroy(finish.cur);
}else
pop_back_aux();
//释放最后一个缓冲区
template <class T, class Alloc, size_t BufSiz>
void deque<T, Alloc, BufSiz>::pop_back_aux(){
deallocate_node(finish.first);
finish.set_node(finish.node-1);
finish.cur=finish.last-1;
destroy(finish.cur);
}
- clear()清除整个缓冲区,保留一个缓冲区,元素被清除
- erase
//根据当前节点前后元素的多少决定向前移动还是往后移
iterator erase(iterator pos){
iterator next=pos;
++next;
difference_type index=pos_start;
if(index<(size()>>1)){
copy_backward(start,pos,next);
pop_front();
}else{
copy(next,finish,pos);
pop_back();
}
return start+index;
}
iterator erase(iterator first, iterator last){
if(first==start&&last==finish){
clear();
return finish;
}else{//
difference_type n=last-first;
difference_type elems_before=first-start;
if(elems_before<(size()-n)/2){
copy_backward(start,first,last);//后移前方元素
iterator new_start=start+n;//标记deque的新起点
destroy(start,new_start);//移动完毕,将冗余的元素析构
//释放冗余的缓冲区
for(map_iterator cur=start.node;cur<new_start.node;++cur)
data_allocator::deallocate(*cur, before_size());
start=new_start;
}else{//前移后方元素
...
}
}
}
- insert,如果在最后或者最前就交给push_back或者push_front; 否则再进行判断,如果前方元素少则push_front第一个元素,并前移元素,后方同理
4.5 stack和queue
- stack是一种FILO的数据结构,只有一个出口。SGI STL默认使用deque实现它
stack以底部容器完成所有工作,并且封装相关接口,实现新的存取机制,这是一种适配器。
- stack没有迭代器
- stack也可以使用list和vector实现,我们可以根据具体场景选择底层容器
- queue是一种FIFO的数据结构,相关定义从上。
4.6 heap
4.6.1 heap概述
- heap不是标准组件,而是作为优先队列的辅助工具依附存在。
- heap存在的动机:为了实现优先队列(每一次取出优先级最高的元素),使用list保证所有元素插入时保序,这导致插入成本为线性;使用BST保序,插入和取出成本都是logn,但是我们能看出这样仍然造成浪费,因为完全没有必要对所有元素保序,只要知道极值元素即可,并且可能需要为其平衡性付出额外代价。于是自然的想法就是能否找到介于这两者之间的数据结构,于是binary heap应运而生。
- heap是一种完全二叉树,并且其子节点和父节点的大小关系保持一致这样就使得整棵树上没有空洞,底层数据结构可以选择数组。
小技巧:为了使得父节点和子节点之间有较为简单的对应关系,我们保留第一个元素,就是#0,这样父节点的下标就等于子节点下标左移一位
4.6.2 heap算法
- push_heap算法:上溯:从新节点一直向上按照父节点必须大于(或小于)的规则进行置换, 时间复杂度O(logn)
- pop_heap算法:下溯:将弹出元素与heap的最后一个叶子节点交换,然后从根部往下按规则置换(注意此时最后一个节点为虚节点,不参与比较)O(logn)
- sort_heap算法:使用pop_heap实现,并将排好序的元素置于heap的最后O(nlogn)
- make_heap算法:从heap最末尾的非叶子节点开始,对每个节点执行下溯操作,考虑摊还代价,从下往上,大部分节点需要下溯的时间很短,
-1层:1n/2
-2层:2n/(2^2)
...
1层:(logn-1)2
0层:(logn)1;
每一层的元素个数构成一个等比数列(首项为n/2,比为1/2),每一层下溯的高度构成一个等差数列(首项为1,差为1),所以时间复杂度的一个差比数列的前logn项和,根据错位相减法最后可以求出总的时间复杂度是O(2n); - heap没有迭代器,不支持遍历功能
4.7 priority_queue
- 严格规定只能从堆顶端存取元素,缺省情况下是使用max_heap实现。它也是一个基于vector的配接器组件
- 定义
template <class T, class Sequence=vector<T>, class Compare=less<typename Sequence::value_type> >
class priority_queue{
public:
typedef typename Sequence::value_type value_type;
...
protected:
Sequence c;
Compare comp;
public:
priority_queue():c(){}
explicit priority_queue(const Compare& x): c(), comp(x){}
priority_queue(InputIterator first, InputIterator last, const Compare& x)
:c(first,last) { make_heap(c.begin(),c.end(),comp);}
...
}
- 以上定义中的相关初始化和其他操作都是基于heap相关操作
- priority_queue没有迭代器
4.8 slist(单向链表)
略
5. 关联式容器
关联式容器主要包括基于红黑树的有序set和map,以及基于hashtable的unordered_set和unordered_map.
5.1 树的概览
树在计算机科学里面是一种别广泛运行的数据结构。其中操作系统会将文件的索引节点和内容存放在树状结构里面;进程的内存区被维护在树状结构里面,用于快速定位;编译器会使用树来构建语法结构;文件压缩使用的huffman算法也需要维护一棵编码树;数据库的索引也是基于一种特殊的多节点B/B+树。在本章中主要介绍一种特殊的平衡树——红黑树,这是一种高度不超过2log(n+1),折衷的平衡树方案。
5.2 红黑树
5.2.1红黑树的性质
- 每一个节点不是红色就是黑色
- 根节点为黑色
- 如果节点为红,其子节点必须为黑
- 任意节点的黑高(从当前节点到叶节点路径上的黑节点个数)必须相同
满足上述四条规则的树就是一棵高度不大于2log(n+1)的平衡树,所以任何对该树的操作都必须维护这四条规则。
5.2.2 红黑树的插入
根据规则4,新增节点必须为红色;根据规则3,新增节点的父节点必须为黑色。新节点的插入未能满足这两条规则时,就必须调整颜色并旋转树形。
假设:
新节点X;
父节点P;
祖父节点G;
叔节点S;
曾祖父节点GG;
根据X的插入位置和外围节点S,GG的颜色,有以下四种情况:
- 状况1:S为黑,X为外侧插入===>对P,G向内做一次单旋转
- 状况2:S为黑,X为内侧插入===>对P,X向外做一次单旋转,并更改G,X的颜色,再对G向内做一次单旋转
- 状况3:S为红,X为外侧插入===>对P,G向内做一次单旋转,并改变X的颜色,如果GG为黑,over,否则进入状况4
- 状况4:S为红,X为外侧输入===>对P,G向内做一次单旋转,并改变X的颜色。此时如果GG为红,则持续往上做,直到不再有父子连续为红的情况
5.2.3 一个自上而下的程序
为了避免遇到状况4中持续向上形成处理时效上的瓶颈,我们可以施行一个自上而下的的程序:
新增节点为A,延着A的路径,将所有子节点都为红色的节点改为红色,子节点改为黑色。如果当前的父节点P也为红色,就像状况1一样做一次单旋转并修改颜色,或者像状况2一样做一次双旋转并修改颜色
在此之后,新节点的插入就简单了:要么直接插入,要么插入后做一次单旋转
5.2.4 RB-tree的迭代器
image.png- RB_tree迭代器属于双向迭代器,但不具备随机定位能力,其提领操作和随机访问操作与list类似,前退和后退较为特殊,前退是寻找比当前元素大的元素,后退是寻找比当前元素小的元素,时间复杂度都是logn
5.2.5 RB-tree的数据结构
template<class Key, class Value,class KeyOfValue,class Compare,class Alloc=alloc>
class rb_tree{
protected:
typedef void* void_pointer;
typedef __rb_tree_node_base* base_ptr;
typedef __rb_tree_node<Value> rb_tree_node;
typedef simple_alloc<rb_tree_node,Alloc> rb_tree_node_allocator;
typedef __rb_tree_color_type color_type;
public:
typedef Key key_type;
typedef Value value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef rb_tree_node* link_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protected:
link_type get_node(){return rb_tree_node_allocator::allocate();}
void put_node(link_type p){rb_tree_node_allocator::deallocate(p);}
link_type create_node(const value_type& x){
link_type tmp=get_node();
__STL_TRY{
construct(&tmp->value_field,x);//使用分配好的空间去初始化节点
}
__STL_UNWIND(put_node(tmp));
return tmp;
}
link_type clone_node(link_type x){
link_type tmp=create(x->value_field);//使用初始值去创建节点,并在下面去初始化节点的其他信息
tmp->color=x->color;
tmp->left=0;
tmp->right=0;
return tmp;
}
void destroy_node(link_type p){
destroy(&p->value_field);//析构内容
put_node(p);//释放内存
}
protected:
size_type node_count;//追踪树的大小
link_type header;//实现上的一个小技巧
Compare key_compare;
link_type& root() const{return (link_type&)header->parent;}
link_type& leftmost()const{return (link_type&)header->left;}
link_type& rightmost() const{return (link_type&)header->right;}
...
static link_type minimum(link_type x){return (link_type)__rb_tree_node_base::minimum(x);}
...
public:
typedef __rb_tree_iterator<value_type,reference,pointer> iterator;
private:
iterator __insert(base_ptr x,base_ptr y,const value_type& v);
link_type __copy(link_type x,link_type p);
void __erase(link_type x);
void init(){
header=get_node();
color(header)=__rb_tree_red;
root()=0;
leftmost()=header;
rightmost()=header;
}
public:
rb_tree(const Compare& comp=Compare()):node_count(0),key_compare(comp){init();}
~rb_tree(){clear();put_node(header);
rb_tree<Key,Value,KeyOfValue,Compare,Alloc>& operator=(const rb_tree<Key,Value,KeyOfValue,Compare,Alloc>& x);
public:
pair<iterator,bool> insert_unique(const value_type&x);
iterator insert_equal(const value_type& x);
...
};
5.2.6 RB_tree的构造和内存管理
- 树状结构的各种操作,最需要注意的就是边界情况的发生,也就是走到根节点时要有特殊的处理,为了简化处理,SGI STL特别为根节点再设计了一个父节点,名为header。
- 每一次插入新节点时,不但要按照RB_tree的规则来调整,并且要维护header的正确性,使其父节点指向根节点,左子节点指向最小节点,右子节点指向最大节点。
5.2.7 RB_tree的操作(插入)
本节涉及红黑树的插入操作,在第一节中已经介绍 ,在这里不做赘述
5.3 set
- set是已红黑树为基础的有序key集合
- set的key不能通过迭代器改变,其底层定义为const_iterator
- set和list相同的某些性质:当客户端对它进行元素新增操作或者删除操作时,操作并不会改变迭代器。
- STL为set提供了集合的相关操作,比如set_intersection、set_union、set_difference、set_symmetric_difference
- lower_bound表示大于他中最小的元素,upper_bound表示小于它中最大的元素,时间复杂度都是logn
5.4 map
- map的键值也不能修改,实值可以修改
- map提供自己的find函数,它比STL的find函数快。后者循序查找,前者二分查找
5.5 hashtable
如何在一定场景下实现常数时间的插入,删除,搜寻等操作,关于这个问题,哈希给出了较好的回答
5.5.1 为什么用hash?
- 上面那个问题提醒我们想要常数时间的操作必须实现某种映射。而数组可以满足这种映射,但是当键的值太大的时候,需要额外开辟太多的空间,这是不现实的,所以必须改变键值即索引的空间问题。
- 进而我们可以通过选择合适的hash函数将键值映射到索引中就可以解决这种问题,但是任何hash函数都没有办法保证一对一的映射,必然会存在碰撞,如何解决碰撞
- 选择合适的表格大小,一般选择远离2^n的质数
- 线性探测:此时负载系数必须小于1。即在映射点附近线性向下寻找,但是存在一次群集的问题,进而导致大量的元素集中在某一个区域,增加查找的负担。
- 二次探测:主要解决一次群集的问题,在hash值的基础上依次递增i的平方,经过论证,入宫表格大小为质数,而且负载系数在0.5以下,则可以确定每插入一个新的元素所需要的探测次数不多于2。但是任然会造成二次群集问题,可以使用复式散列来解决
- 开链:在每一个表格元素上维护一个list,这样对负载系数的要求较低,但是会增加内存的成本,STL就是使用的这种方案
5.5.2 hashtable的桶子和节点
- 数据结构
typedef struct __hashtable_node{
__hashtable_node* next;
Value val;
}node;
vector<node* ,Alloc> buckets;
- 开链法不要求大小为质数,但是尽量使用质数,首先维护一个包含28个质数的表格备用,大致合理分布在整形数范围内,并提供接口直接调用获取这些质数
- hashtable的重整需执行rehashing的过程,这个在redis里面使用异步的rehashing实现,并且一致性hash可以解决分布式hash单调性的问题
5.5.7 hash functions
- 该函数是计算元素位置的
6. 算法
6.1 STL算法简述
每一个STL算法都需要传入一个由迭代器控制的容器区间。传入迭代器的类型由算法本身对于元素操作方式所决定,高级别的迭代器可以被传入要求低级别迭代器的算法,反之则无效,然而即时无效该错误也无法在编译器检测出来,因为在泛型函数中,迭代器类型是一种型别参数,而不是真实的类型可以被编译期类型检查机制检测出来。
另外算法本身有时候对一些微小的变化实行更加细粒度的动态控制,比如算法中比较函数的行为控制,它会允许用户传入一个自定义仿函数,以便采用其他控制策略。所有的数值算法在包含在头文件<numeric>中,其他的算法包含在<algorithm>
6.2 算法的泛化过程
算法泛化的一个重要目标是:如何在原算法框架基础上,毫无压力的加入新的算法得以操作的类型,而不会出现兼容性问题,也就是隔离数据结构本身。
STL采取的方法:将算法操作的实体抽象出来,即用不同的迭代器来表示容器对象的型别差异,然而这种差异又尽在掌握之中,因为我们往往只需要以某种方式操作容器的元素以及对操作行为本身的行为加以控制即可,而不需要真正去洞察容器内部的结构和底层 的实现,这样的泛化就于是只关注迭代器的型别以及定义在迭代器上共通的一组操作。
- 隔离数据结构最好的做法是一切联系仅通过传入参数,而对于传入参数以外的任何细节我们都不应该过多涉及甚至应该完全避免触碰,因为它阻碍了我们的泛型之路。于是传入一个首尾标志的区间就是一个明智的选择。
- 传入区间的前闭后开的设计符合我们对区间操作的习惯,也就是判断的最后一个元素往往是无效退出的。
- 数值的转递应该改为常引用的传递,这样可以避免大对象传递带来的额外开销
注意按值传递、按引用转递、按常引用传递三者的开销和使用场景
- 按值传递:整体拷贝一份临时对象用于函数内部使用,对实参没有影响,内置类型参数传递且不该变参数本身的情况下使用
- 按引用传递:传递一个对象的别名,对用户而言等同于对象本身,内部是使用地址引用工作的,适用于大对象且需要改变对象本身的时候
- 按常引用传递:大对象且不需要改变内容的时候,可以避免临时对象拷贝造成的开销
6.3 序列容器算法实例
6.3.1 power
template <class T, class Integer, class MonoidOperation>
T power(T x, Integer n, MonoidOperation op){
if(n==0)
return identity_element();
else{
while((n&1)==0){
n>>=1;
x=op(x,x);
}
T result=x;
n>>=1;
while(n!=0){
x=op(x,x);
if((n&1)==0)
result=op(result,x);
n>>=1;
}
return result;
}
}
iter_swap
iter_swap使用了迭代器的value type,使用以下语句即可定义一个临时变量
typename iterator_traits<ForwardIterator1>::value_type tmp=*a
copy
STL的copy算法的完整脉络如果输入区间和输出区间重叠,赋值顺序需要多加讨论,result位于[first,last)之内时,我们需要使用copy_backward(),反之我们不用在意。
- copy是改变迭代器所指的对象,所以输出迭代器不能为空,必须已经分配了空间
- 注意偏特化版本,即在“参数为原生指针形式”的前提下,进一步探测指针所指之物是否具有trivial assignment operator,如果是就使用效率更高的memmove来完成赋值操作,负责使用赋值运算符操作
- vector迭代器其实是原生指针。所以关于它的型别判断是直接使用memmove操作的
- 用户自定义类型即时其包含的操作符本身的trivial的,放在vector里面作为容器判断偏特化型别时也不能作为trivial处理。因为默认支持的自定义型别验证的编译器很少,一般只是对标量型别进行验证。所以我们需要手动去设定
__STL_TEMPLATE_NULL struct __type_traits<C>{
typedef __false_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_contructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __false_type is_POD_type;
}
6.4 set 相关算法
set在stl里面默认是使用红黑树实现,是有序的。本节介绍的四个算法是在有序基础上实现的
6.4.1 set_union并集
- 数学中因为S1和S2中的元素不要求唯一,所以输出区间中的相同元素个数是max(m,n),在stl set中这里的m和n都必须小于或等于1
- 具体实现步骤
- 比较当前元素,谁小谁赋给输出集,并前进一个,知道其中一个为空
- 将两个区间分别往结果集中复制(空集自然不会复制)
6.4.2 set_intersection
具体实现和并集很像,只要在相等时赋值即可
6.4.3 set_difference差集
具体实现和上述算法很像,只需要在小于时赋值即可
6.4.4. symmetric_difference
具体实现和上述算法很像,只是在非等于的时候赋值即可
6.5 其他算法
merge
template<class InputIterator1, class InputIterator2,class OutputIterator, class Compare>
OutputIterator merge(InputIterator1 first1, InputIterator1 last1,InputIterator2 first2, InputIterator2 last2,OutputIterator results,Compare cmp ){
while(first!=last1&&first2!=last2){
if(cmp(*first2,*first1){
*result=*first2;
++first2;
}else{
*result=*first1;
first1++;
}
++result;
}
return copy(first2,last2,copy(first1,last1,result));
}
partition
template <class BidirectionalIterator,class Predicate>
BidirectionalIterator partition(BidirectionalIterator first,
BidirectionalIterator last,
Predicate pred){
while(true){
while(true)
if(first==last)
return first;
else if (pred(*first))//判断数值是否为偶数
++first;
else
break;
--last;
while(true)
if(first==last)
return first;
else if(!pred(*first))
--last;
else
break;
iter_swap(first,last)
++first;
}
}
reverse_hsy和rotate——针对forward的算法
template<class ForwardIterator>
inline void reverse_hsy(ForwardIterator first,ForwardIteraor last){
ForwardIterator ret=first;
first++;
while(first!=last){
ret->next=first->next;
first->next=ret;
ret=first++;
}
first=ret;
}
template<class ForwardIterator>
inline void rotate(ForwardIterator first,ForwardIterator middle,ForwardIterator last){
if(first==middle||middle==last) return;
__rotate(ForwardIterator first,ForwardIterator middle,ForwardIterator last,distance_type(first),iterator_category(first));
}
template <class ForwardIterator,class Distance>
void __rotate(ForwardIterator first,ForwardIterator middle,ForwardIterator last,Distance*,forward_iterator_tag){
for(ForwardIterator i=middle;;){
iter_swap(first++,i++);
if(first==middle){
if(i==last) return;
middle=i;
}else if(i==last)
i=middle;
}
}
template <class BidirectionalIterator,class Distance>
void __rotate(BidirectionalIterator first,
BidirectionalIterator middle,
BidirectionalIterator last,
Distance*,
bidirectional_iterator_tag){
reverse(first,middle);
reverse(middle,last);
reverse(first,last);
}
template <class RandomAccessIterator,class Distance>
void __rotate(RandomAccessIterator first,
RandomAccessIterator middle,
RandomAccessIterator last,
Distance*,
random_access_iterator_tag){
Distance n=__gcd(last-first,middle-first);
while(n--)
__rotate_cycle(first,last,first+n,middle-first,value_type(first));
}
template <class EuclideanRingElement>
EuclideanRingElement __gcd(EuclideanRingElement m,EuclideanRIngElement n){
while(n){
EuclideanRingElement t=m%n;
m=n;
n=t;
}
return m;
}
lower_bound(运用于有序区间)
作用:从左到右返回第一个“不小于value”的元素,如果value大于[first,last]内的任意一个元素,则返回last。
template <class ForwardIterator,class T,class Distance>
ForwardIterator __lower_bound(ForwardIterator first,
ForwardIterator last,
const T& value,
Distance*,
forward_iterator_tag){
Distance len=0;
distance(first,last,len);
Distance half;
ForwardIterator middle;
while(len>0){
half = len>>1;
middle=first;
advance(middle,half);
if(*middle<value){//对于upper_bound这里改为小于或等于
first=middle;
++first;
l len=len-half-1;
}else
len=half;
}
return first;
}
template<class RandomAccessIterator,class T,class Distance>
RandomAccessIterator __lower_bound(RandomAccessIterator first,
RandomAccessIterator last,
const T& value,
Distance*,
random_access_iterator_tag){
Distance len=last-first;
Distance half;
RandomAccessIterator middle;
while(len>0){
half=len>>1;
middle=first+half;
if(*middle<value){//对于upper_bound这里改为大于或等于
first=middle+1;
len=len-half-1;
}else{
len=half;
}
}
return first;
}
二分查找
这里的二分查找是基于lower_bound的
关键判断
ForwardIterator i=lower_bound(first,last,value);
return i!=last&&!(value<*i);//或者改为return i!=last&&!comp(value, *i);
next_permulation
template<class BidirectionalIterator>
inline bool next_permutation(BidirectionalIterator first,BidirectionalIterator last){
if(first==last) return false;
BidirectionalIterator i=last,j=last;
i--;
if(i==first) return false;
while(*--i>=*--j){//从后往前,找到递增的两个数,如果是prev_permulation 这里改为大于或等于
if(i==first) {//当前为最大排列
reverse(first,last);//获取最小排列
return false;
}
}
reverse(j,last);//翻转当前位置往后的所有元素
iter_swap(i,lower_bound(j,last,*i+1));
return true;
}
排序
template <class RandomAccessIterator>
void __insertion_sort(RandomAccessIterator first,
RandomAccessIterator last){
if(first==last) return;
for(RandomAccessIterator i=first+1;i!=last;++i){
__linear_insert(first,i,value_type(first));//子区间插入
}
}
template<class RandomAccessIterator,class T>
inline void __linear_insert(RandomAccessIterator first,
RandomAccessIterator last,T*){
T value=*last;
if(value<*first){
copy_backward(first,last,last+1);//如果要插入的元素比当前元素小,则直接将所有元素后移一个单位,并在头部插入该元素
*first=value;
}else{
__unguarded_linear_insert(last,value);//子区间检索并插入
}
}
template<class RandomAccessIterator,class T>
inline void __unguard_linear_insert(RandomAccessIterator last,T value){
RandomAccessIterator next=last;
--next;
while(value<next*){//从后往前检索,第一个元素已经比较过,所有不用再考虑边界问题
*last=*next;
last=next--;
}
*last=value;
}
//SGI STL sort
template <class RandomAccessIterator>
inline void sort(RandomAccessIterator first,RandomAccessIterator last){
if(first!=last){
__introsort_loop(first,last,value_type(first),__lg(last-first)*2);
__final_insertion_sort(first,last);//对于快速排序分割出来的元素进行排序
}
}
template <class Size>
inline Size __lg(Size n){
Size k;
for(k=0;n>1;n>>=1)
++k;
return k;
}
const int __stl_threshold=16;
template <class RandomAccessIterator,class T>
RandomAccessIterator __unguard_partition(RandomAccessIterator first,
RandomAccessIterator last,T pivot){
while(true){
while(*first<pivot) --first;
last--;
while(*last>=pivot) --last;
if(first>=last) return first;
iter_swap(first,last);
++first;
}
}
template <class RandomAccessIterator,class T,class Size>
void __introsort_loop(RandomAccessIterator first,
RandomAccessIterator last, T*,Size depth_limit){
while(last-first>_stl_threshold){
if(depth_limit==0){
partial_sort(first,last,last);//这里防止分割恶化,当分割次数过多时采用堆排序改善情况
return;
}
--depth_limit;
RandomAccessIterator cut=__unguard_partition(first,last,T(__median(*first,*(first+(last-first)>>1),*(last-1))));
__introsort_loop(cut,last,value_type(first),depth_limit);
last=cut;//右边处理完了再来处理左边
}
}
template<class RandomAccessIterator>
void __unguard_insertion_sort(RandomAccessIterator first,RandomAccessIterator last){
__unguard_insertion_sort_aux(first, last, value_type(first));
}
template<class RandomAccessIterator,class T>
void __unguard_insertion_sort_aux(RandomAccessIterator first,RandomAccessIterator last,T*){
for(RandomAccessIterator it=first;it!=last;++it)
__unguard_linear_insert(it, T(*it));
}
template <class RandomAccessIterator>
void __final_insertion_sort(RandomAccessIterator first,RandomAccessIterator last){
if(last-first>__stl_threshold){
__insertion_sort(first,first+__stl_threshold);//insertion sort
__unguard_insertion_sort(first+__stl_threshold,last);//后续元素一个一个插入即可,注意这里是将这个区间内的每一个元素针对整个大区间做一次插入排序
}else{
__insertion_sort(first,last);
}
}
以上排序过程分为三个步骤
- 内排序,当元素数量大于阈值且递归次数足够深时,采用基于堆排序的部分排序
- 当元素数量足够多且递归深度较小时,使用中位数分割法进行快速排序
- 当递归到元素数量小于阈值时,此时使用快排的效率减低,退出内排序,执行插入排序法
equal_range(找到某个值可插入的区间)
emplate <class RandomAccessIterator,class T,class Distance>
pair<RandomAccessIterator,RandomAccessIterator>
__equal_range(RandomAccessIterator first,
RandomAccessIterator last,
const T& value,
Distance*,
random_access_iterator_tag){
Distance len=last-first;
Distance half;
RandomAccessIterator middle,left,right;
while(len>0){
half=len>>1;//1. 获取数量的一半
middle=first+half;//2. 获取中间位置的值
if(*middle<value){寻找value值所在的区间,和常规二分法思路一致
first=middle+1;
len=len-half-1;
}else if(value<*middle)
len=half;
else{
left=lower_bound(first,middle,value);//获取左边界,lower_bound包含相等的值
right=upper_bound(++middle,first+len,value);//获取右边界,upper_bound不包含相等的值
return pair<RandomAccessIterator,RandomAccessIterator>(left,right);
}
}
return pair<RandomAccessIterator,RandomAccessIterator>(first,first);
}
inplace_merge
template <class BidirectionalIterator>
inline void inplace_merge(BidrectionalIterator first,
BidirectionalIterator middle,
BidirectionalIterator last){
if(first==middle||middle==last) return ;
__inplace_merge_aux(first,middle,last,value_type(first),distance_type(first));
}
template <class BidirectionalIterator,class T,class Distance>
inline void __inplace_merge_aux(BidirectionalIterator first,
BidirectionalIterator middle,
BidirectionalIterator last,
T*,Distance*){
Distance len1=0;
distance(first,middle,len1);//序列1的长度
Distance len2=0;
distance(middle,last,len2);//序列2的长度
temporary_buffer<BidirectionalIterator ,T> buf(first,last);//临时缓冲区,
if(buf.begin()==0)
__merge_without_buffer(first,middle,last,len1,len2);//缓存区分配失败,使用原址归并,原址归并采取的是比较互换算法,算法的时间复杂度为O(n*n)
else
__merge_adaptive(first,middle,last,len1,len2,buf.begin(),Distance(buf.size()));//缓存区分配成功也可能是部分成功,具体请况在下面讨论
}
template <class BidirectionalIterator,class Distance,class Pointer>
void __merge_adaptive(BidirectionalIterator first,
BidirectionalIterator middle,
BidirectionalIterator last,
Distance len1.Distance len2,
Pointer buffer,Distance buffer_size){
if(len1<=len2&&len1<=buffer_size){//缓冲区可以容纳序列1
Pointer end_buffer=copy(first,middle,buffer);//直接使用外归并算法
merge(buffer,end_buffer,middle,last,first);
}else if(len2<=buffer_size){//缓冲区只能容纳序列2
Pointer end_buffer=copy(middle,last,buffer);//使用外归并算法
__merge_backward(first,middle,buffer,end_buffer,last);
}else{//缓冲区既不能容纳序列1也不能容纳序列2,此时就是考虑如何减小归并的长度,使得缓冲区可以被用上
BidirectionalIterator first_cut=first;
BidirectionalIterator second_cut=middle;
Distance len11=0;
Distance len22=0;
if(len1>len2){
len11=len1/2;//缩短序列1的长度,为切割点1
advance(first_cut,len11);
second_cut=lower_bound(middle,last,*first_cut);//即序列2中第一个比切割点1大的元素,为切割点2
distance(middle,second_cut,len22);
}else{
len22=len2/2;
advance(second_cut,len22);
first_cut=lower_bound(first,middle,*second_cut);
distance(first,first_cut,len11);
}
BidirectionalIterator new_middle=__rotate_adaptive(first,first_cut,middle,len11,len22,buffer,buffer_size);
__merge_adaptive(first,first_cut,new_middle,len11,len22,buffer,buffer_size);
__merge_adaptive(new_middle,second_cut,last,len1-len11,len2-len22,buffer,buffer_size);
}
}
template <class BidirectionalIterator1,class BidirectionalIterator2,class Distance>
BidirectionalIterator1 __rotate_adaptive(BidirectonalIterator1 first,
BidirectionalIterator1 middle,
BidirectionalIterator1 last,
Distance len1,Distance len2,
BidirectionalIterator2 buffer,
Distance buffer_size){
BidirectionalIterator2 buffer_end;
if(len1>len2&&len2<=buffer_size){
buffer_end=copy(middle,last,buffer);
copy_backward(first,middle,last);
return copy(buffer,buffer_end,first);
}else if(len1<=buffer_size){
buffer_end=copy(first,middle,buffer);
copy(middle,last,first);
return copy_backward(buffer,buffer_end,middle);
}else{
rotate(first,middle,last);//使用原址旋转方式
advance(first,len2);
return first;
}
}
总结:基础算法的性能提升就在于对细粒度操作的精确分类和对空间和时间的有效权衡,这里的inplace_merge操作就是在考虑各种细粒度场景下有限空间的最优解决方案。
nth_element
template<class RandomAccessIterator>
inline void nth_element(RandomAccessIterator first,
RandomAccessIterator nth,
RandomAccessIterator last){
__nth_element(first,nth,last,value_type(first));
}
template<class RandomAccessIterator,class T>
void __nth_element(RandomAccessIterator first,
RandomAccessIterator nth,
RandomAccessIterator last,
T*){
while(last-first>3){
RandomAccessIterator cut=__unguard_partition(first, last, T(__median(*first,*(first+(last-first)/2),*(last-1))));
if(cut<=nth)
first=cut;
else
last=cut;
}
__insertion_sort(first,last);
}
mergesort(基于inplace_merge)
template<class BidirectionalIterator>
void mergesort(BidirectionalIterator first,BidirectionalIterator last){
typename iterator_traits<BidirectionalIterator>::difference_type n=distance(first,last);
if(n==0||n==1)
return ;
else{
BidirectionalIterator mid=first+n/2;
mergesort(first,mid);
mergesort(mid,last);
inplace_merge(first,mid,last);
}
}
### 关注memory pool、iterator_traits、deque、RB-tree、hash、quicksort、mergesort
#7 仿函数
###7.1 仿函数简介
仿函数事实上就是一种功能性封装类型,它对函数操作符operator()进行重载,然后重新定义某一算子功能,其对象被传入算法作为一个动态功能嵌入到容器操作中去。传统意义上我们的函数指针也可作为形参传入,但是函数指针不能满足STL对抽象性的要求(函数指针毕竟不是一种抽象性更高的自定义类型,而c++的类可以完成更为复杂的功能),并且它也无法和STL的其他组件搭配。
###7.2 可配接的关键
- STL仿函数应该有能力被函数配接器修饰。为了有配接能力,每一个仿函数必须定义自己的相应型别。这些型别是为了让配接器能够取出,获得仿函数的某些信息。相应型别都只是一些typedef,所有必要操作在编译期就全部完成了,对程序的执行效率没有任何影响。
- 其型别主要用来表现函数参数型别和传回值型别。<stl_function.h>定义了两个classes,分别代表一元和二元仿函数,其中没有任何members,唯有一些型别定义。任何仿函数,只要依个人需求选择继承其中一个class,便自动拥有了那些型别,也就拥有了配接能力
#####7.2.1 unary_function
一元仿函数的基类
#####7.2.1 binary_function
二元仿函数的基类
###7.3 算术类仿函数
###7.4 关系运算类仿函数
###7.5 逻辑运算类仿函数
#8 配接器
adaptor的定义:将一个class的接口转换为另外一个class的接口,使原本因接口不兼容而不能合作的classes可以一起运作。
注:感觉有点像装饰器,就是在一个类型的接口上装饰出另外一组接口,然后封装出一个新的类型
### 8.1 配接器简介与分类
- STL所提供的各种配接器中,改变仿函数接口者,我们称之为function adapter,改变容器接口者,我们称之为container adapter。改变迭代器接口者,我们称之为iterator adapter
#####8.1.1 应用于容器
queue和stack基于deque上的配接器,使用包含的方式将底层容器嵌入到新类型中
#####8.1.2 应用于迭代器
insert iterator、reverse iterators、iostream iterators.
#####8.1.3 应用于仿函数
灵活度最强的配接器功能,可以实现bind、negate、compose、以及对一般函数或者成员函数的修饰(使其成为一个仿函数),在<stl_function.h>中是这么规定的
- bind:bind2nd(less<int>(),12),这里的意思是将仿函数的第二个参数绑定为12,其仍然返回一个仿函数类型
- compose:compose1(f(x),g(x)),复合函数,及g(x)的返回值作为f(x)的参数
- 对于一般函数和成员函数的兼容:可以传入普通函数,但是无法实现配接功能,除非加上ptr_fun()和mem_fun()。
###8.4 function adapters
和其他配接器一样,函数配接器不过是包含一个函数成员并在其之上使用一些操作,并返回一个仿函数对象的封装结构罢了。