码迷,mamicode.com
首页 > 其他好文 > 详细

STL源码剖析

时间:2015-07-08 20:31:44      阅读:140      评论:0      收藏:0      [点我收藏+]

标签:

  花了两天时间略读了一下《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;
}
View Code

  实现了四个不同的函数重载,再增加一个上层函数advance(),粹取出迭代器的类型,并调用相应的__advance版本。

技术分享
template<class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n){
        __advance(i,n,iterator_traits<InputIterator>::iterator_category());
}
View Code

  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));
}
View Code

  嗯,就先总结到这些吧。其他的一些东西要么之前了解得比较熟了,那么印象不是很深刻,就不写出来了。

 

STL源码剖析

标签:

原文地址:http://www.cnblogs.com/yplhh/p/4631192.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!