STL源码剖析
花了两天时间略读了一下《stl源码分析》,看了个大体,对于细节并没有深究。之所以想翻翻这本书,主要是想看看stl中的特性、适配器的具体实现。看完之后收获还是蛮大的,模板的各种组合让我眼前一亮,下面大概总结一些内容。
1.内存分配:sgi内存分配采用两级实现,对于大内存块的申请(大于128k)由第一级实现,第一级实现较简单,直接调用malloc和free。对于小于128k的内存请求,由第二级实现。第二级使用了内存池,维护了一些链表,分别指向不同大小的空闲内存块,这些内存块都是8的倍数的大小,分别是8k,16k,24k,...,128k。分配内存时,根据请求大小找到相应链表的表头,然后从表头摘取一个结点块,回收的时候也是从表头插入。第二级内存分配的实现方式跟linux操作系统的空闲内存管理很相似,不过内存回收的时候有区别,linux内存管理还涉及到空闲块的合并。
2.迭代器:要设计一种迭代器,必须对容器的具体实现有丰富的了解,所以把迭代器的实现交给容器的设计者,这样就可以是容器的实现细节得以封装,所以每一种STL容器都提供了专属的迭代器。迭代器中的end()具体指向了哪个位置?对于vector这种由连续内存块来实现的容器,end()指向数组之后的一个位置。而对于list,map,set这种由”结点“实现的容器,会有专门的一个结点,该结点含有一些重要的信息,比如指向开头结点的指针。为什么有些迭代器只能是向前的?因为其容器的内部实现决定了它的迭代器只能是前向的,比如单向链表的迭代器当然只能是向前的了。
3.容器:deque的结构挺复杂的,使用了分段数组,并使用了一个map数组来映射不同分段数组的地址,通过封装,使其使用起来就像是个连续的数组一样。stack和queue的内部结构使用的是deque。vector容器增长的平均时间复杂度是O(1)。具体计算,大家可以自己想想。
4.traits:c++的特性让我见识了c++抽象的强大,通过增加一层抽象,使问题的解决变得很微妙。这部分主要利用了模板的类型推断能力。通过偏特化,能够粹取出类类型和原始指针的特性。印象很深的一个例子是advance函数。
template<class InputIterator, class Distance> inline void __advance(InputIterator& i, Distance n, input_iterator_tag){ whie(n--) ++i; } template<class ForwardIterator, class Distance> inline void __advance(ForwardIterato& i, Distance n, fowrd_iterator_tag){ __advance(i,n,input_iterator_tag()); } template<class BidirectionalIterator, class Distance> inline void __advance(BidirectionalIterator& i, Distance n, bidirectional_iterator_tag){ if(n >= 0){ while(n--) ++i; }else{ while(n++) --i; } } template<class RandomAccessIterator, class Distance > linue void __advance(RandomAccessIterator& i, Distance n, random_access_iterator_tag){ i += n; }
实现了四个不同的函数重载,再增加一个上层函数advance(),粹取出迭代器的类型,并调用相应的__advance版本。
template<class InputIterator, class Distance> inline void advance(InputIterator& i, Distance n){ __advance(i,n,iterator_traits<InputIterator>::iterator_category()); }
5.适配器:适配器的实现主要是在内部包含了一个对象,在外面展示不同的接口,内部实际上还是间接使用了其所包含的对象。为什么说为了让一个函数对象在stl是可适配的?因为在外层类中要使用到内部包含类型的某些”特性“,比如bind1st,就要使用到内部二元函数仿函数的first_argument_type。所以为了能够被适配,一般都要继承unary_function和binary_function。
6.采用辅助函数:我们知道使用模板类声明一个对象,需要给出具体的类型,有时手动地给出具体类型会很麻烦,那么有没有办法让编译器自动推导出具体的类型呢?这里使用了”增加一层抽象“,使用模板函数,模板函数具有自动推导的功能,利用模板函数推导出来的类型来具现模板类。比如下面的例子。
template<class Operation> class binder1st : public unary_function<typename Operation::second_argument_type, typename Operation::result_type>{ protected: Operation op; typename Operation::first_argument_type value; public: binder1st(const Operation& x, const typename Operation::first_argument_type& y):op(x),value(y){} typename Operation::result_type operator()(const typename Operation::second_argument_type& x)const{ return op(value,x); } }; template<class Operation, class T> inline binder1st<Operation> bind1st(const Operation& op, const T& x){ typedef typename Operation::first_argument_type arg1_type; return binder1st<Operation>(op,arg1_type(x)); }
嗯,就先总结到这些吧。其他的一些东西要么之前了解得比较熟了,那么印象不是很深刻,就不写出来了。