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 -- 容器源码探索的主要内容,如果未能解决你的问题,请参考以下文章
STL标准库 & 范型编程学习笔记:vectorarrayforward_list深度探索