C++标准库 STL -- 容器源码探索

Posted 张三和李四的家

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++标准库 STL -- 容器源码探索相关的知识,希望对你有一定的参考价值。

文章内容为侯捷老师的《C++标准库与泛型编程》的学习笔记

第二讲:容器源码探索

文章目录

源码之前,了无密码。

GP 编程

  • Containers 和 Algorithm 团队各自忙自己的事情,其间通过 Iterator 进行沟通。
  • Algorithm 通过 Iterator 确定操作的范围,Iterator 从 Container 取用元素。
template<class T>
inline const T& max(const T& a, const T& b)

	return b > a ? b: a;


template<class T, class Compare>
    inline const T& max(const T&a, const T& b)

    return compare(a,b) ? b : a;

上述代码实现了两种排序方式,第一种是 普通数据类型的比较,当然也可以用来比较class,不过需要重载 运算符 <,第二种则不需要,但需要提供额外的比较条件——compare 。

使用:

bool strLonger(const string& s1, const string& s2)

    return s1.size() < s2.size();


cout << "max of zoo and hello: "
    << max(string("zoo"), string("hello")) << endl;
cout << "longest of zoo and hello:"
    << max(string("zoo"), string("hello"), strLonger) << endl;

链表不能使用std::sort来进行排序,因为sort 底层在排序时,用到了随机迭代器,可以随意对迭代器进行++,而链表不具备这样的能力。它不是一个连续空间的数据结构。

所有的 algorithm ,其内最终涉及元素本身的操作,无非就是比大小。

操作符重载和模板

操作符重载。不同的操作符会有不同的操作数。比如 a++ 、a--分别只需要一个操作数。而a+b 则需要两个操作数。

表格来源

指针具有的操作,迭代器都会进行重载一遍,为了实现将迭代器当成指针使用的目的。比如一个链表的迭代器。

template<class T, class Ref, class Ptr>
    struct __list_iterator 
        typedef __list_iterator<T, Ref, Ptr> self;
      	typedef __list_node<T>* link_type;
        link_type node;
        
        reference operator*() const  return (*node).data; 
        pointer operator->() const  return &(operator*()) ; 
        self & operator++()  node = ( link_type)((*node).next) ); return *this; 
        self operator++(int)  self tmp = *this; ++*this; return tmp;//我比较纳闷,这里的int 有没有被用上。但源码的确是这样的。
    ;

在使用模板时,你要告诉编译器,你传入什么类型进入模板类中。使用尖括号明白的告诉编译器使用的类型,然后跟上变量名,最后在小括号中,将初始化数值传入类中。

complex<double> c1(2.5, 1.6);

把它们 (编程逻辑) 里面的类型暂定为一个符号,而不写死,等到真正使用的时候,才去把它确定下来。这个就是模板

分配器

调用alloc 会得到一块内存,但实际上只用到了一部分。如下图:

其中只用到了 蓝色部分,灰色是debug 时产生的。砖红色是一些cookie。绿色的是 为调整到某一个边界所额外开销。附加的这些东西是基本固定的,你要的某一块越大,它所附加东西的比例就越小。你要的东西越小,附加东西的比例就越大。

  • cookie 记录申请内存块的大小,所以free 只需要一个指针即可回收内存

直接malloc带来的问题

一个string 的类型只有四个字节。当你往一个容器中放入100万个string 时,额外的开销会比100万个 string 还要多。

//分配512 ints

int *p = allocator<int>().allocate(512, (int*)0);
allocator<int>().decallocate(p, 512);

//allocator<int>() 是一个object

/* 扩展
	cout << vector<int>().size() << endl;
	cout << vector<int>(10).size() << endl;
	cout << vector<int>(10, 100).front() << endl;
*/

良好分配器的解决方式

一个分配器有16个链表,每个链表负责不同的大小。第0个链表负责 8字节大小的内存,第n 负责 8*n 字节大小的内存,第16个负责128个字节的内存。所有需要内存的容器都向这个分配器要内存。如果分配器没有对应大小的内存,分配器才会去向操作系统要——要大块,然后做切割。割成一小块块的。切出来的一块块用链表串起来。结果是这切出来的每一个块都不带cookie。

其它的分配器

目前大多用的是 allocator分配器,当然除了 常规的allocator外,还提供了一些额外的分配器。比如:__poll_alloc

容器 - 结构与分类

容器会分为两大类:序列式容器和关联式容器。其中有一些容器是由一些容器衍生出来的,这里的衍生,并非继承而是复合。

比如 stack 和 queue 由 deque衍生而来,stack(queue) 中有一个 deque,它所表现出来的能力由deque 所提供,所以stack 和 queue 又是一种容器适配器。

set ,map, multimap,multiset 的功能由rb_tree(红黑树) 实现。

List

    template <class T>
    struct __list_node 
        typedef void* void_pointer;
        void_pointer prev;
        void_pointer next;
        T data;
    ;

    template<class T, class Ref, class Ptr>
    struct __list_iterator
        typedef T   value_type;
        typedef Ref reference;
        typedef Ptr pointer;
        typedef bidirectional_iterator_tag iterator_category;//使用一种标签来表明它的特性
        typedef ptrdiff_t difference_type;  //固定的长度,如果容器添加的元素超过这个长度,可能会爆掉

        typedef __list_iterator<T, Ref, Ptr> self;
        typedef __list_node<T>* link_type;
        link_type node;

        reference operator*() const  return (*node).data; 
        pointer operator->() const  return &(operator *());///等同于 ===> &((*node).data) 可实现元素类型-> 的效果
        self& operator++ ()
         node = (link_type)((*node).next); return *this; 

        self operator++ (int)
         self tmp = *this;  ++*this;  return tmp;
        /**
          后置++的代码执行流程
          1. 记录原值
            self tmp = *this;
            此处的不会调用operator* 来处理this,执行的是 copy cotr ,用以创建 tmp 并以 *this 为初值。
            __list_iterator(const iterator& x) : node(x.node) 
            当*this 当做ctor 的参数后,就不调用operator* 的重载函数
          2. 进行操作
*/
        /**
          关于 前置++ 的返回值为引用,后置++ 返回值为对象的原因:
          向整型操作致敬,因为 C++ 允许整型两次前置++,而不允许两次后置++。
*/
    ;


    template <class T, class Alloc = alloc>
    class List 
    protected:
        typedef __list_node<T> list_node;
    public:
        typedef list_node* link_type;
        typedef __list_iterator<T, T&, T*> iterator;
    protected:
        link_type node;

    ;

有趣的是,GCC4.9版本的 list 是使用继承来拥有 next 和 prev

   template <typename _Tp>
    struct _List_iterator
        typedef _Tp* pointer;
        typedef _Tp* reference;
    ;

    template<typename _Tp,
             typename _alloc = std::allocator<_Tp>>
    class list: protected _List_base<_Tp, _Alloc>
    
    public:
        typedef _List_iterator<_Tp> iterator;
    ;

    struct _List_node_base 
        _List_node_base * _M_next;
        _List_node_base * _M_prev;
    ;

    template <typename _Tp>
    struct _List_node
            : public _List_node_base 
        _Tp _M_data;
    ;
    /**
      通过继承的方式来获取 头部指针和尾部指针
*/

Iterator需要遵循的原则

Iterators 迭代器 是处于容器和算法之间的桥梁,负责从容器中获取数据,也负责回答算法提出的问题。比如:负责告诉算法接受的迭代器 是什么样的数据类型。

所以迭代器中要设计出5种关联类型。

template <class _Iterator>
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;
;

比如List 的迭代器

template<class _Tp, class _Ref, class _Ptr>
struct _List_iterator 
  typedef _List_iterator<_Tp,_Tp&,_Tp*>             iterator;
  typedef _List_iterator<_Tp,const _Tp&,const _Tp*> const_iterator;
  typedef _List_iterator<_Tp,_Ref,_Ptr>             _Self;

  typedef bidirectional_iterator_tag iterator_category;
  typedef _Tp value_type;
  typedef _Ptr pointer;
  typedef _Ref reference;
  typedef ptrdiff_t difference_type;
;

Traits 特性,特征,特质

Iterator Traits 用来区分 class iterators 和 no-class iterators。

如果是class iterator T,可直接T::valueType,来获取迭代器的数据类型。

如果是class * iterator pT。我无法通过pT->valueType;

所以要通过一个中间层,这个中间层,被称为萃取机。

   /**
     * 为了解决 传入的 iterator 是 class 还是 pointer,
     *  下面制定了泛化版本 iterator_traits 和 偏特化版本 iterator_traits
*/
namespace my 
    template <class T >     //如果 T 是 class iterator
    struct iterator_traits 
        typedef typename T::value_type value_type;
    ;

    template <class T>  //如果 T 是 pointer to T
    struct iterator_traits<T*> 
        typedef T value_type;
    ;

    template <class T>  //如果 T 是 pointer to const T
    struct iterator_traits<const T*> 
        typedef T value_type;
    ;


完整的 iterator_traits。

template <class _Iterator>
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;
;
template <class _Tp>
struct iterator_traits<_Tp*> 
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef _Tp*                        pointer;
  typedef _Tp&                        reference;
;

template <class _Tp>
struct iterator_traits<const _Tp*> 
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef const _Tp*                  pointer;//这里的指针和引用都有常量的修饰
  typedef const _Tp&                  reference;
;

除了有迭代器的 Traits 外,还有

vector

template <class T, class Alloc = alloc>
class vector 
public:
    typedef T value_type;
    typedef value_type * iterator;
    typedef value_type& reference;
    typedef size_t size_type;
protected:
    //不同于链表,所有连续空间存储元素的容器都可以使用指针作为迭代器。
    iterator start;
    iterator finish;
    iterator end_of_storage;
public:
    iterator begin()  return start;
    iterator end()  return finish;
    size_type size() const
     return size_type(end() - begin());

    size_type capacity()
     return size_type( end_of_storage - begin() );

    bool empty() const  return begin() == end();
    reference operator[] (size_type n)
     return *(begin() + n);
    reference front()  return *begin();
    reference back()  return *finish();

;

deque

其中的 map 是一个vector, 其中的元素是一个个的指针,这些指针分别指向各个缓冲区的buff。

容器结构源码和容器迭代器源码

/**
 *  //sz 为元素A的大小,如果元素A大小 大于 512 则一个缓冲区放入一个元素。
    //反则就放入 size_t(512 / sizeof(A)) 个数量
    inline size_t __deque_buf_size(size_t n, size_t sz)
     return n != 0 ? n :( sz < 512 ? size_t(512 / sz) :size_t(1) );
**/
inline size_t _deque_buf_size(size_t n, size_t sz)

    return n != 0 ? n : ( sz < 512 ? size_t(512/sz) : size_t(1));


template <class T, class Ref, class Ptr, size_t BufSize>
struct __deque_iterator 
    typedef random_access_iterator_tag iterator_catogory;   //随机访问迭代器的类型
    typedef T value_type;
    typedef Ptr pointer;
    typedef Ref reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef T** map_pointer;
    typedef __deque_iterator self;

    T* cur;//元素的位置
    T* first;//缓冲区的头部
    T* last;//缓冲区的尾部
    map_pointer node;

    reference operator*() const  return *cur; 
    pointer operator->() const  return &(operator *());

    self & operator++()
    
        ++cur;                      //切换到下一个元素
        if (cur == last)           //如果抵达缓冲区尾端
            set_node(node + 1);     //就切换到下一个节点
            cur = first;            //cur 再指向于 first    cur 是指向缓冲区的元素,first 和 end 是缓冲区的首尾元素
        

        return * this;
    

    self operator ++(int)
    
        self tmp = *this;
        ++*this;
        return tmp;
    

    difference_type operator-(const self& x) const
    
        //buffer_size 等同于  _deque_buf_size
        return difference_type(buffer_size()) * (node - x.node - 1) +
                (cur - first) + (x.last - x.cur);
        /**
          node - x.node - 1 其中减一的原因是,x.node 所在的buff 上,存在元素未放满的情况
*/
    

    reference operator[] (difference_type n) const
     return *(*this + n);

    /// 依次修改 node first last 的位置
    void set_node(map_pointer new_node)
    
        node = new_node;
        first = *new_node;
        last = first + difference_type(buffer_size());
    
;

/**
    BufSiz 指的是每个 buffer 容纳的元素个数
*/
template <class T, class Alloc = alloc,
          size_t BufSize = 0>
class deque 
public:
    typedef T value_type;
    typedef value_type* pointer;
    typedef __deque_iterator<T, T&, T*, BufSiz> iterator;//自定义类作为迭代器
    typedef size_t size_type;
protected:
    typedef pointer* map_pointer;
protected:
    iterator start;//指向容器头部的迭代器 ,start.cur 是容器的第一个元素,*start 也是指向容器的第一个元素
    iterator finish;//指向容器尾部的迭代器, finish.cur 是容器的最后一个元素,finish 是指向容器最后一个元素的下一个位置。要取最后一个元素,需要--finish.
    map_pointer map;
    size_type map_size;
public:
    iterator begin()  return start; 
    iterator end()  return finish; 
    size_type size()  return finish - start;
    size_type max_size() const  return size_type(-1); 
;

queue

#include "deque.h"

/*
 * queue 依赖 deque ,头尾可进可出的特性,实现了队列形式的先进先出
*/

template <class T, class _Sequence = deque<T> >//也可以使用其它容器作为 _Sequence 的参数,如果该容器可以实现 queue 中的成员函数。 ,比如 List
class queue 
public:
  typedef typename _Sequence::value_type      value_type;
  typedef typename _Sequence::size_type       size_type;
  typedef          _Sequence                  container_type;

  typedef typename _Sequence::reference       reference;
  typedef typename _Sequence::const_reference const_reference;
protected:
  _Sequence c;
public:

  bool empty() const  return c.empty(); 
  size_type size() const  return c.size(); 
  reference front()  return c.front(); 
  const_reference front() const  return c.front以上是关于C++标准库 STL -- 容器源码探索的主要内容,如果未能解决你的问题,请参考以下文章

C++的探索路20标准模板库STL之STL的基本概念与容器

STL标准库 & 范型编程学习笔记:vectorarrayforward_list深度探索

STL标准库 & 范型编程学习笔记:vectorarrayforward_list深度探索

一些关于广泛使用的C++标准库STL的思考

一些关于广泛使用的C++标准库STL的思考

C++标准库STL部分代码学习(源码之前,了无秘密)