STL六大组件
- 容器(containers):是一种class template,里面包含各种数据结构。
- 算法(algorithms):是一种function template,里面包含各种算法。
- 迭代器(iterators):是所谓的泛型指针,每个容器都有自己的专属的迭代器,知道如何遍历自己的元素。
- 仿函数(functors):是一种重载了operator()的class或class template,可作为算法的某种策略。
- 配接器(adapters):是一种用来修饰容器或仿函数或迭代器接口的东西。
- 配置器(allocators):是一个实现了动态空间配置,空间管理,空间释放的class template,负责空间配置与管理。
空间配置器
构造
- 分配新的空间
- 在新空间上构造对象
template <class T>
inline T* _allocate(...) {
...
T* tmp = (T*)(::operate new((size_t)(size * sizeof(T))));
...
}
template <class T1, class T2>
inline void _construct(T1* p, const T2& value) {
new(p) T1(value); //placement new.
}
operator new
(1)只分配所要求的空间,不调用相关对象的构造函数。当无法满足所要求分配的空间时,则
->如果有new_handler,则调用new_handler,否则
->如果没要求不抛出异常(以nothrow参数表达),则执行bad_alloc异常,否则
->返回0
(2)可以被重载
(3)重载时,返回类型必须声明为void*
(4)重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t
(5)重载时,可以带其它参数
Placement new
placement new 是重载operator new 的一个标准、全局的版本,它不能够被自定义的版本代替(不像普通版本的operator new和operator delete能够被替换)。
对象的分配
在已分配的缓存区调用placement new来构造一个对象。
Task *ptask = new (buf) Task
SGI中的空间配置与释放(std::alloc)
SGI设计哲学:
- 向system heap要求空间
- 考虑多线程状态
- 考虑内存不足时的应变措施
- 考虑过多“小型区块”可能造成的碎片问题
内存分配中使用::operator new()与::operator delete()分配与释放相当于molloc()与free().
考虑第四点设计了双层配置器,第一层直接调用malloc()和free().
配置内存小于128bytes时,使用第二级配置器:
使用16个自由链表负责16种小型区块的次级配置能力。如果内存不足转一级配置器。
8 16 24 32 40 48 56 64 72 80 88 96 104 112 120 128
迭代器
迭代器类似于一种智能指针,比较重要的行为有内容提领和成员访问。
迭代器相应型别
为了让迭代器适应不同种类的容器即泛化,需利用function template的参数推导机制推导型别。
偏特化:如果class template拥有一个以上的template参数,我们可以针对其中某个template参数进行特化工作(我们可以在泛化设计中提供一个特化版本)。
trans编程技法
声明内嵌型别推导函数返回值:
template <class T>
struct MyIter {
typedef T value_type; //内嵌型别
T* ptr;
MyIter(T* p=0) : ptr(p) {}
T& operator*() const { return *ptr; }
//...
}
template <class I>
typename I::value_type //这一整行是func的返回值型别
func(I ite) {
return *ite;
}
MyIter<int> ite(new int(8));
cout << func(ite);
萃取型别:
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 T>
struct iterator_traits<T*> {
typedef T value_type;
};
STL根据经验,定义了迭代器最常用到的五种类型:value_type、difference_type、pointer、reference、iterator_category,任何开发者如果想将自己开发的容器与STL结合在一起,就一定要为自己开发的容器的迭代器定义这五种类型,这样都可以通过统一接口iterator_traits萃取出相应的类型,下面列出STL中iterator_traits的完整定义:
tempalte<typename I>
struct iterator_traits
{
typedef typename I::iterator_category iterator_category;
typedef typename I::value_type value_type;
typedef typeanme I:difference_type difference_type;
typedef typename I::pointer pointer;
typedef typename I::reference reference;
};
重点提一下iterator_category:
根据移动特性与施行操作,迭代器被分为五类:
- Input Iterator:只读
- Output Iterator:只写
- Forward Iterator:允许“写入型”算法(例如replace())在此种迭代器所形成的区间上进行读写操作。
- Bidirectional Iterator:可双向移动。
- Random Access Iterator:前四种迭代器都只供应一部分指针算术能力(前三种支持 operator++,第四种再加上operator–),第五种则涵盖所有指针算术能力,包括p+n, p-n, p[n], p1-p2, p1
序列式容器
元素可序但未必有序
vector
vector底层为动态数组,分配策略为:
- 如果vector原大小为0,则配置1,也即一个元素的大小。
- 如果原大小不为0,则配置原大小的两倍。
当然,vector的每种实现都可以自由地选择自己的内存分配策略,分配多少内存取决于其实现方式,不同的库采用不同的分配策略。
当以两倍空间增长时之前分配的内存空间不可能被使用,这样对于缓存并不友好。
相关连接:https://www.zhihu.com/question/36538542
vector的迭代器
使用vector迭代器时要时刻注意vector是否发生了扩容,一旦扩容引起了空间重新配置,指向原vector的所有迭代器都将失效。
vector维护的是一个连续线性空间,与数组array一样,所以无论其元素型别为何,普通指针都可以作为vector的迭代器而满足所有必要的条件。vector所需要的迭代器操作,包括operator*,operator->,operator++,operator–,operator+=,operator-=等,普通指针都具有。
vector提供了Random Access Iterators。
vector的数据结构
vector底层为连续线性空间
list
list容器完成的功能实际上和数据结构中的双向链表是极其相似的,list中的数据元素是通过链表指针串连成逻辑意义上的线性表
对于迭代器,只能通过“++”或“–”操作将迭代器移动到后继/前驱节点元素处,而不能对迭代器进行+n或-n的操作。增加任何元素都不会使迭代器失效。删除元素时,除了指向当前被删除元素的迭代器外,其它迭代器都不会失效。