容器(container) 、分配器(allocator) 、算法(algorithms) 、迭代器(iterator) 、适配器(adaptor) 、仿函数(functor)
VC6、BC++ 、以及SGI也有一个符合”部分“标准(一级分配器)、名为allocator的分配器, 它只是以 ::operator new和 ::operator delete 完成 allocate()和 deallocator(),而new、delete最后都会调用C底层的malloc()、free(),但malloc申请内存总是会有额外开销,总是带有cookie记录内存大小,占用8字节。所以SGI的allocator不建议使用,效率不佳。
SGI还有一个特殊的空间分配器std::alloc ,不接受任何参数。
不能采用标准写法:
vector> iv; //in VC or cB
必须这么写:
vector iv; //in GCC
通常,C++内存分配和释放的操作如下:
class Foo {...};
Foo *pf = new Foo; //配置内存,然后构造对象
delete pf; // 将对象析构,然后释放内存
new内含2阶段操作:
delete也含2阶段操作:
STL allocator将两阶段操作区分开来。内存配置有alloc::allocate() 负责,内存释放由alloc::deallocate()负责;对象构造操作::construct(),对象析构::destroy()负责。皆定义在
SGI对内存分配与释放的设计哲学如下:
C++的内存分配基本操作是::operator new(),内存释放基本操作是::operator delete()。这两个全局函数相当于C的malloc()和free()函数。
SGI正是以malloc和free()完成内存的分配与释放。
SGI 第一级分配器_malloc_alloc_template 以malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重配置操作,并实现类似C++ new handler机制(一旦malloc 无法完成任务,在丢出 std::bad_alloc 异常状态之前,会先调用有客户端指定的处理例程。)
第二级分配器 _default_alloc_temolate。由于每次用malloc申请内存时,都要有额外开销。
SGI第二级配置器的做法是,如果区块够大,>128 bytes,就用第一级配置器处理。当区块<128 bytes,则以内存池(memory pool)管理,此方法又称为次层配置。每次配置一大块内存,并维护对应的自由链表(free-list)。为了方便管理,二级配置器会主动将任何小额区块内存需求量上调至8的倍数(例如客户端要求30 bytes,就自动调整为32 bytes)并维护16个free-list,各自管理大小分别为8,16,24.。。。128bytes 的小额区块。
重新填充free-list的函数refill()
chunk_alloc()函数从内存池申请空间,根据end_free-start_free判断内存池中剩余的空间
上图中,一开始就调用chunk_alloc(32,20),于是malloc()分配40个32bytes区块,其中第1个交出,另19个交给free-list[3]维护,余20个留给内存池;接下来客户调用chunk_alloc(64,20),此时free_list[7]空空如也,必须向内存池申请。内存池只能供应(32*20)/64=10个64bytes区块,就把这10个区块返回,第1个交给客户,余9个由free_list[7]维护。此时内存池全空。接下来再调用chunk_alloc(96,20),此时free-list[11]空空如也,必须向内存池申请。而内存池此时也为空,于是以malloc()分配40+n(附加量)个96bytes区块,其中第1个交出,另19个交给free-list[11]维护,余20+n(附加量)个区块留给内存池...
迭代器是一种泛化指针,最重要的编程工作是对operator* 和 operator-> 进行重载。每一种STL容器都提供专属的迭代器。
template
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的意义是,若 I 有定义自己的value type ,那么通过traits 萃取出的value_type 就是I::value_tye。
多一层间接层(itetator_traits)的好处是能拥有特化版本。当iterator是个原生指针时:
//以C++内建的ptrdiff_t(定义于头文件)作为原生指针的difference type
//针对原生指针的偏特化版本
template
struct iterator_traits{
//原生指针是一种Random Access Iterator
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
};
//针对原生pointer-to-const的偏特化版本
template
struct iterator_traits{
//原生指针是一种Random Access Iterator
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef const T* pointer;
typedef const T& reference;
};
最常用到的迭代器相应型别有五种: value_type 迭代器所指对象类型;difference_type 两个迭代器之间的距离,因此也用来表示一个容器的最大容量。对于连续空间的容器而言,头尾之间的距离就是最大容量,如果一个泛型算法提供计数功能,例如STL的count(),返回值就必须使用迭代器的difference_type。reference_type 、pointer_type、iterator_category 迭代器种类。
(直线与箭头代表的并非继承关系,而是concept 与 reference 关系)
任何迭代器都应该提供五个内嵌相应型别,以利于traits萃取。STL提供了一个iterator class如下,每个新设计的迭代器都可以继承它,保证符合STL的规范。
iterator_traits 负责萃取迭代器的特性
_ _type_traits:负责萃取类型的特性,包括:
通过使用__type_traits,在对某个类型进行构造、析构、拷贝、赋值等操作时,就可以采用最有效率的措施。这对于大规模而操作频繁的容器,有着显著的效率提升。
4.1 容器的概观与分类
上图中的“衍生”并非“派生(inherit)”,而是内含(contain)关系。例如heap内含一个vector,priority-queue内含一个heap,stack和queue都含一个deque,set/map/multiset/multimap都内含一个RB-tree,has_x都内含一个hashtable。
4.1.1 序列式容器
C++语言本身提供了一个序列式容器array,STL另外再提供vector、list、deque、stack、queue、priority-queue等,其中stack和queue只是deque改头换面而已,技术上被归类为一种配接器。
array是静态空间,一旦配置了就不能改变;vector与array非常相似,但是vector是动态空间,随着元素的加入,内部机制会自动扩充以容纳新元素 。
SGI STL中vector的定义。使用线性连续空间,以两个迭代器start 和finish 分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage 指向整块连续空间(含备用空间)的尾端。
template
class vector{
.....
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(); }
}
当我们以push_back()将新元素插入vector尾端时,该函数首先检测是否还有备用空间,如果有,就直接在备用空间构造元素,并调整迭代器finish,使vector变大,若没有备用空间,扩充空间(重新配置、移动数据、释放原空间)。
void push_back(const T& X){
if(finish!=end_of_storage){ //还有备用空间
construct(finish,x);
++finish;
}
else
insert_aux(end(),x) ; //分配内存
}
vector操作的实现
常见的vector操作包括:
插入操作可能造成vector的3个指针重新配置,导致原有的迭代器全部失效
STL list的节点结构
template
struct _list_node{
typedef void* void_pointer;
void_pointer prev; //类别为void*,其实可设为 _list_node*
void_pointer next;
T data;
}
SGI list不仅是一个双向链表,还是一个环状双向链表。所以它只需要一个指针,便可完整表现整个链表:
template
class list {
protected:
typedef __list_node list_node;
public:
typedef list_node* link_type;
protected:
link_type node; //只要一个指针,便可表示整个环状双向链表
};
iterator begin() { return (link_type)((*node).next); }
iterator end() { return node; }
size_type size() const {
size_type result = 0;
distance(begin(), end(), result);
return result;
}
list 是一个双向链表,迭代器必须具备前移、后移的操作,所以list提供的是Bidirectional Iterators
list操作的实现
节点操作
//删除某个节点的例子
ite=find(ilist.begin(),ilist.end(),1)
if(ite!=0)
cout<< *(ilist.erase(ite)) <
链表操作
list 内部提供 transfer 迁移操作,将连续范围的元素迁移到某个特定位置之前。这个操作作为其它复杂操作如splice,sort,merge等奠定良好的基础。list 不使用STL算法的sort(),必须使用自己的sort() member function(是一个快排),因为STL算法sort()只接受RamdonAccessIterator。
deque是一种双向开口的连续线性空间。deque的大小是40byte
deque和vector最大的差异:
dequeue 数据结构
deque采用一块所谓的map作为主控(中控器)。这里所谓的map是指一小块连续空间,其中每个元素都是一个指针,指向另一段、(较大的)连续线性空间,称为缓冲区。缓冲区才是deque的存储空间主体。SGI STL允许我们指定缓冲区大小,默认值0表示使用512bytes缓冲区
temlate
class deque{
public: //Basic types
typedef T value_type;
typedef value_type* pointer;
typedef size_t size_type;
...
public:
typedef __deque_iterator iterator; //迭代器类型
protected: //Internal typedefs
//元素的指针的指针
typedef pointer* map_pointer;
protected: //Data members
iterator start; //第一个节点的迭代器
iterator finish; //最后一个节点的迭代器
map_pointer map; //指向map,map是块连续空间
//其每个元素都是个指针,指向一个节点(缓冲区)
size_type map_size; //map的大小,即内有多少个指针
...
};
deque的中控器、缓冲区、迭代器的关系如下图:
deque 迭代器
deque是分段连续空间,维持其“整体连续”假象的任务,落在了迭代器的operator++和operator-- 两个子运算上。
operator ++ 实现
self& operator++(){
++cur; //切换至下一个元素
if(cur==last){ //如果已达所在缓冲区的尾端
set_node(node+1); //就切换至下一节点(下一个缓冲区)
cur=first; //的第一个元素
}
return *this;
}
void set_node(map_pointer new_node){
node=new_node;
first=*new_node;
last=first+difference_type(buffer_size());
}
push_back()操作
void push_back(const value_type& t){
if(finish.cur != finish.last-1){
//最后缓冲区尚有一个以上的备用空间
construct(finish.cur,t); //直接在备用空间上构造元素
++ finish,cur;
}
else //最后缓冲区已无(或只剩一个)元素备用空间
push_back_aux(t);
}
template
void deque::push_back_aux(const value_type& t){
value_type t_copy=t;
reserve_map_at_back(); //如符合条件则必须重换一个map
*(finish.node + 1)= allocate_node(); //配置一个新节点(缓冲区)
_STL_TRY{
construct(finish.cur,t_copy);
finish.set_node(finish.node+1);
finish.cur=finish.first;
}
}
deque操作的实现
一种先进后出的数据结构。没有迭代器
以某种既有容器作为底部结构,将其接口改变,使之符合“先进先出”的特性,形成一个stack,是很容易做到的。deque是双向开口的数据结构,若以deque为底部结构并封闭其头端开口,便形成一个stack。
由于stack以底部容器完成其所有工作,而具有”修改某物接口,形成另一种风貌“的性质者,称为适配器。因此,STL stack往往不被归类为容器,而被归类为容器适配器。
先进先出的数据结构。若以deque为底部结构并封闭其底端的出口和前端的入口,便形成一个queue。
heap 并不属于STL容器组件,扮演priority queue 的助手。priority queue 允许用户以任何次序将任何元素放入容器内,但取出时一定是从优先级最后的元素开始取。binanry max heap 正具有这样的特性,适合作为priority queue 的底层容器。
heap是一颗完全二叉树,完全二叉树使用数组实现,因此使用一个vector作为heap的结构,然后通过一组xxx_heap算法,使其符合heap的性质。
上溯(在此之前应该push_back):push_heap
下溯 pop_heap(在此之后应该pop_back)
sort_heap
make_heap
优先队列。缺省情况下priority_queue 利用一个max_heap完成。
以下为SGI STL中priority_queue的定义:
template ,
class Compare = less >
class priority_queue {
public:
typedef typename Sequence::value_type value_type;
typedef typename Sequence::size_type size_type;
typedef typename Sequence::reference reference;
typedef typename Sequence::const_reference const_reference;
protected:
Sequence c; //底层容器
Compare comp; //元素大小比较标准
public:
priority_queue() : c() {}
explicit priority_queue(const Compare& x) : c(), comp(x) {}
//以下用到的make_heap()、push_heap()、pop_heap()都是泛型算法
//构造一个priority queue,首先根据传入的迭代器区间初始化底层容器c,然后调用
//make_heap()使用底层容器建堆
template
priority_queue(InputIterator first, InputIterator last, const Compare& x)
: c(first, last), comp(x) { make_heap(c.begin(), c.end(), comp); }
template
priority_queue(InputIterator first, InputIterator last)
: c(first, last) { make_heap(c.begin(), c.end(), comp); }
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
const_reference top() const { return c.front(); }
void push(const value_type& x) {
//先利用底层容器的push_back()将新元素推入末端,再重排heap
__STL_TRY {
c.push_back(x);
push_heap(c.begin(), c.end(), comp);
}
__STL_UNWIND(c.clear());
}
void pop() {
//从heap内取出一个元素。但不是真正弹出,而是重排heap,然后以底层容器的pop_back()
//取得被弹出的元素
__STL_TRY {
pop_heap(c.begin(), c.end(), comp);
c.pop_back();
}
__STL_UNWIND(c.clear());
}
};