上篇简单介绍了gcc4.8提供的几种allocator的实现方法和作用,这是所有stl组件的基础,容器必须通过allocator申请分配内存和释放内存,至于底层是直接分配释放内存还是使用内存池等方法就不是组件需要考虑的事情。这篇文章开始分析gcc4.8 stl的容器源码实现。stl的容器分为序列式容器和关联式容器,前者包括vector,list,queue以及stack等常用数据结构,后者包含了map,set以及hash table等比较高级的结构,本文就从使用最广泛也是最基础的vector开始。

一、 vector的定义

vector继承自_Vector_base, _Vector_base包装了内存管理器_M_impl,下面是Vector_base的定义,由于代码过多,删除了一些不妨碍理解的源码:

template<typename _Tp, typename _Alloc>
struct _Vector_base
{
//指定特定数据类型的内存分配器
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Tp>::other _Tp_alloc_type;
typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer
pointer; struct _Vector_impl
: public _Tp_alloc_type
{
pointer _M_start; //使用空间起始位置
pointer _M_finish; //使用空间结束位置
pointer _M_end_of_storage; //可使用空间结束位置
_Vector_impl()
: _Tp_alloc_type(), _M_start(), _M_finish(), _M_end_of_storage()
{ }
_Vector_impl(_Tp_alloc_type const& __a)
: _Tp_alloc_type(__a), _M_start(), _M_finish(), _M_end_of_storage()
{ }
_Vector_impl(_Tp_alloc_type&& __a)
: _Tp_alloc_type(std::move(__a)),
_M_start(), _M_finish(), _M_end_of_storage()
{ }
//vector数据交换,只交换内存地址,不交换数据
void _M_swap_data(_Vector_impl& __x)
{
std::swap(_M_start, __x._M_start);
std::swap(_M_finish, __x._M_finish);
std::swap(_M_end_of_storage, __x._M_end_of_storage);
}
};
public:
_Vector_base()
: _M_impl() { }
_Vector_base(const allocator_type& __a)
: _M_impl(__a) { }
_Vector_base(size_t __n)
: _M_impl()
{ _M_create_storage(__n); }
_Vector_base(size_t __n, const allocator_type& __a)
: _M_impl(__a)
{ _M_create_storage(__n); }
_Vector_base(_Vector_base&& __x) //移动构造函数,只交换3个指针,不copy数据
: _M_impl(std::move(__x._M_get_Tp_allocator()))
{ this->_M_impl._M_swap_data(__x._M_impl); }
//释放内存空间
~_Vector_base()
{ _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage
- this->_M_impl._M_start); }
public:
_Vector_impl _M_impl;
pointer
//通过 _M_impl申请内存
_M_allocate(size_t __n)
{ return __n != ? _M_impl.allocate(__n) : ; }
void
//通过 _M_impl释放内存
_M_deallocate(pointer __p, size_t __n)
{
if (__p)
_M_impl.deallocate(__p, __n);
}
private:
void
_M_create_storage(size_t __n)
{
this->_M_impl._M_start = this->_M_allocate(__n);
this->_M_impl._M_finish = this->_M_impl._M_start;
this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
}
};

Vector_base专门负责vector的内存管理,内部类_M_impl通过继承_Tp_alloc_type得到内存分配释放的功能,_M_allocate和_M_deallocate分别分配和释放vector所用内存,vector只需要负责元素构造和析构。

rebind函数的原型是:

template<typename _Tp1>  struct rebind
{ typedef allocator<_Tp1> other; };

用于得到特定类型的分配器,此处直接得到vector元素类型_Tp的分配器,其实并没有体现其作用,在list中我们将会看到它的技巧所在。

vector继承于vector_base,具体内容后面讲解

template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>
{
}

默认使用 std::allocator内存分配器,即上篇文章介绍的new_allocator,简单封装了operator new和operator delete 。

类结构图如下,

二 、vector的迭代器

vector使用的是__normal_iterator迭代器,

typedef __gnu_cxx::__normal_iterator<pointer, vector> iterator; 
typedef __gnu_cxx::__normal_iterator<const_pointer, vector> const_iterator;

__normal_iterator定义于stl_iterator.h,封装了vector元素的指针,

template<typename _Iterator, typename _Container>
class __normal_iterator
{
protected:
_Iterator _M_current;
typedef iterator_traits<_Iterator> __traits_type;
public: _GLIBCXX_CONSTEXPR __normal_iterator() : _M_current(_Iterator()) { }
explicit
__normal_iterator(const _Iterator& __i) : _M_current(__i) { }
//拷贝构造函数
template<typename _Iter>
__normal_iterator(const __normal_iterator<_Iter,
typename __enable_if<
(std::__are_same<_Iter, typename _Container::pointer>::__value),
_Container>::__type>& __i)
: _M_current(__i.base()) { }
reference
operator*() const
{ return *_M_current; }
pointer
operator->() const
{ return _M_current; }
__normal_iterator&
operator++()
{
++_M_current;
return *this;
}
__normal_iterator
operator++(int)
{ return __normal_iterator(_M_current++); }
__normal_iterator&
operator--()
{
--_M_current;
return *this;
}
__normal_iterator
operator--(int)
{ return __normal_iterator(_M_current--); }
reference
operator[](const difference_type& __n) const
{ return _M_current[__n]; } __normal_iterator&
operator+=(const difference_type& __n)
{ _M_current += __n; return *this; }
__normal_iterator
operator+(const difference_type& __n) const
{ return __normal_iterator(_M_current + __n); }
__normal_iterator&
operator-=(const difference_type& __n)
{ _M_current -= __n; return *this; }
__normal_iterator
operator-(const difference_type& __n) const
{ return __normal_iterator(_M_current - __n); }
const _Iterator&
base() const
{ return _M_current; }
};

_M_current是指向迭代器位置的指针,这是一个随机访问型指针,operator+和operator-等移动操作可以直接移动到目的地,非随机访问型指针只能一步步移动。

vector还会使用reverse_iterator,即逆序迭代器,顾名思义,其移动方向与普通迭代器相反

explicit reverse_iterator(iterator_type __x) : current(__x) { }
reference operator*() const
{
_Iterator __tmp = current;
return *--__tmp; //取前一个位置上的值
}
reverse_iterator& operator++()
{
--current;
return *this;
}
reverse_iterator& operator--()
{
++current;
return *this;
}

reverse_iterator的值并不在当前位置上,而是前一个位置上的值,++和--跟普通迭代器的移动方向正好相反。

三、 vector的数据存储

vector内存由_M_impl中的M_start,_M_finish,_M_end_of_storage三个指针管理,所有关于地址,容量大小等操作都需要用到这三个指针:

   iterator begin() _GLIBCXX_NOEXCEPT
{ return iterator(this->_M_impl._M_start); }
iterator end() _GLIBCXX_NOEXCEPT
{ return iterator(this->_M_impl._M_finish); }
reverse_iterator rbegin() noexcept
{ return reverse_iterator(end()); }
reverse_iterator rend() noexcept
{ return reverse_iterator(begin()); }
size_type size() const _GLIBCXX_NOEXCEPT
{ return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
size_type capacity() const _GLIBCXX_NOEXCEPT
{ return size_type(this->_M_impl._M_end_of_storage
- this->_M_impl._M_start); }
bool empty() const _GLIBCXX_NOEXCEPT
{ return begin() == end(); }

结构图如下:

_M_finish和_M_end_of_storage之间的空间没有数据,有时候这是一种浪费,c++11提供了一个很有用的函数shrink_to_fit(),将这段未使用空间释放,主要调用了下面代码,

_Tp(__make_move_if_noexcept_iterator(__c.begin()),
__make_move_if_noexcept_iterator(__c.end()),
__c.get_allocator()).swap(__c);

先把M_start,_M_finish之间的数据拷贝出来,再和原vector做一次swap,新的vector只包含M_start与_M_finish之间的元素,原vector空间被释放。

四、 vector的构造方式

vector提供了多种构造函数,

    vector() : _Base() { }  //使用默认内存分配器
explicit vector(const allocator_type& __a) //指定内存分配器
: _Base(__a) { }
//初始化为n个__value值,如果没指定就使用该类型默认值
explicit vector(size_type __n, const value_type& __value = value_type(),
const allocator_type& __a = allocator_type())
: _Base(__n, __a)
{ _M_fill_initialize(__n, __value); }
//拷贝构造函数
vector(const vector& __x)
: _Base(__x.size(),
_Alloc_traits::_S_select_on_copy(__x._M_get_Tp_allocator()))
{ this->_M_impl._M_finish =
std::__uninitialized_copy_a(__x.begin(), __x.end(),
this->_M_impl._M_start,
_M_get_Tp_allocator());
}
//c++11的移动构造函数,获取__x的M_start,_M_finish,_M_end_of_storage,并不需要数据拷贝
vector(vector&& ) noexcept
: _Base(std::move(__x)) { }
//从list中拷贝数据,也是c++11才有的
vector(initializer_list<value_type> __l,
const allocator_type& __a = allocator_type())
: _Base(__a)
{
_M_range_initialize(__l.begin(), __l.end(),
random_access_iterator_tag());
//支持vector使用两个迭代器范围内的值初始化,除了stl的迭代器,也可以是数组地址
template<typename _InputIterator,
typename = std::_RequireInputIter<_InputIterator>>
vector(_InputIterator __first, _InputIterator __last,
const allocator_type& __a = allocator_type())
: _Base(__a)
{ _M_initialize_dispatch(__first, __last, __false_type()); }
//只析构所有元素,释放内存由vector_base完成
~vector() _GLIBCXX_NOEXCEPT
{ std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
_M_get_Tp_allocator()); }

五、 vector的元素操作

vector的元素操作方式很丰富,这里只讨论insert函数,insert函数在指定位置前插入元素,可能需要移动数据和分配空间。

typename vector<_Tp, _Alloc>::iterator
vector<_Tp, _Alloc>::
insert(iterator __position, const value_type& __x)
{
const size_type __n = __position – begin();
//插入到最后一个位置,相当于push_back
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage
&& __position == end())
{
_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);
++this->_M_impl._M_finish;
}
else
{
_M_insert_aux(__position, __x);
}
return iterator(this->_M_impl._M_start + __n);
} template<typename _Tp, typename _Alloc>
void
vector<_Tp, _Alloc>::
_M_insert_aux(iterator __position, const _Tp& __x)
{
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
{ //内存空间足够
_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
_GLIBCXX_MOVE(*(this->_M_impl._M_finish
- )));
++this->_M_impl._M_finish;
//__position后的元素依次向后移动一个位置
_GLIBCXX_MOVE_BACKWARD3(__position.base(),
this->_M_impl._M_finish - ,
this->_M_impl._M_finish – );
//目标地址赋值
*__position = _Tp(std::forward<_Args>(__args)...);
}
else
{
//内存加倍
const size_type __len =
_M_check_len(size_type(), "vector::_M_insert_aux");
const size_type __elems_before = __position - begin();
pointer __new_start(this->_M_allocate(__len));
pointer __new_finish(__new_start);
__try
{
//给position位置赋值
_Alloc_traits::construct(this->_M_impl,
__new_start + __elems_before,
std::forward<_Args>(__args)...);
__x);
__new_finish = ;
//拷贝position位置前元素
__new_finish
= std::__uninitialized_move_if_noexcept_a
(this->_M_impl._M_start, __position.base(),
__new_start, _M_get_Tp_allocator()); ++__new_finish;
//拷贝position位置后元素
__new_finish
= std::__uninitialized_move_if_noexcept_a
(__position.base(), this->_M_impl._M_finish,
__new_finish, _M_get_Tp_allocator());
}
__catch(...)
{
if (!__new_finish)
_Alloc_traits::destroy(this->_M_impl,
__new_start + __elems_before);
else
std::_Destroy(__new_start, __new_finish, _M_get_Tp_allocator());
_M_deallocate(__new_start, __len);
__throw_exception_again;
} //析构原vector所有元素
std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
_M_get_Tp_allocator());
//释放原vector内存空间
_M_deallocate(this->_M_impl._M_start,
this->_M_impl._M_end_of_storage
 this->_M_impl._M_start);
//vector内存地址指向新空间
this->_M_impl._M_start = __new_start;
this->_M_impl._M_finish = __new_finish;
this->_M_impl._M_end_of_storage = __new_start + __len;
}
} //内存分配策略并不是简单的加倍,如果n小于当前size,内存加倍,否则内存增长n。
size_type
_M_check_len(size_type __n, const char* __s) const
{
if (max_size() - size() < __n)
__throw_length_error(__N(__s)); const size_type __len = size() + std::max(size(), __n);
return (__len < size() || __len > max_size()) ? max_size() : __len;
}

stl源码分析之vector的更多相关文章

  1. STL源码分析《4》----Traits技术

    在 STL 源码中,到处可见 Traits 的身影,其实 Traits 不是一种语法,更确切地说是一种技术. STL库中,有一个函数叫做 advance, 用来将某个迭代器(具有指针行为的一种 cla ...

  2. STL源码分析《3》----辅助空间不足时,如何进行归并排序

    两个连在一起的序列 [first, middle) 和 [middle, last) 都已经排序, 归并排序最核心的算法就是 将 [first, middle) 和 [middle, last) 在  ...

  3. STL 源码分析《1》---- list 归并排序的 迭代版本, 神奇的 STL list sort

    最近在看 侯捷的 STL源码分析,发现了以下的这个list 排序算法,乍眼看去,实在难以看出它是归并排序. 平常大家写归并排序,通常写的是 递归版本..为了效率的考虑,STL库 给出了如下的 归并排序 ...

  4. STL源码分析读书笔记--第二章--空间配置器(allocator)

    声明:侯捷先生的STL源码剖析第二章个人感觉讲得蛮乱的,而且跟第三章有关,建议看完第三章再看第二章,网上有人上传了一篇读书笔记,觉得这个读书笔记的内容和编排还不错,我的这篇总结基本就延续了该读书笔记的 ...

  5. stl源码分析之allocator

    allocator封装了stl标准程序库的内存管理系统,标准库的string,容器,算法和部分iostream都是通过allocator分配和释放内存的.标准库的组件有一个参数指定使用的allocat ...

  6. STL源码分析与实现-stl_list容器

    1. stl_list 介绍 今天我们来总结一下stl_List, 通过之前介绍单链表的文章,其实对链表的基本操作已经十分熟悉了,那对于stl_list,无非就是链表结构不一样,至于其中的增删改查的细 ...

  7. STL 源码分析六大组件-allocator

    1. allocator 基本介绍 分配器(allocator))是C ++标准库的一个组件, 主要用来处理所有给定容器(vector,list,map等)内存的分配和释放.C ++标准库提供了默认使 ...

  8. STL 源码分析《2》----nth_element() 使用与源码分析

    Select 问题: 在一个无序的数组中 找到第 n 大的元素. 思路 1: 排序,O(NlgN) 思路 2: 利用快排的 RandomizedPartition(), 平均复杂度是 O(N) 思路 ...

  9. STL源码解析之vector自实现

    最近仔细阅读了下侯捷老师的<STL源码剖析>的前三章内容:空间配置器.迭代器.traits技术,为了加强理解和掌握,特参照源码自实现Vector,以下对相关重点知识进行说明. 1. vec ...

随机推荐

  1. Python 多线程 使用线程 (二)

    Python中实现多线程需要使用到 threading 库,其中每一个 Thread类 的实例控制一个线程. Thread类 #类签名 def __init__(self, group=None, t ...

  2. 关于"XML 文档(2, 2)中有错误:不应有 <xml xmlns=''>"错误

    XML文件名 <?xml version="1.0" encoding="utf-8"?> <Config xmlns:xsi="h ...

  3. javascript中获取dom元素高度和宽度

    javascript中获取dom元素高度和宽度的方法如下: 网页可见区域宽: document.body.clientWidth网页可见区域高: document.body.clientHeight网 ...

  4. DPDK运行出现EAL Error reading from file descriptor 23 Input output error

    原因 dpdk不支持该网卡导致,需要修改一行代码,跳过dpdk pci 检查. 解决方法 修改lib/librte_eal/linuxapp/igb_uio/igb_uio.c 将文件中该行修改 pc ...

  5. DPDK测试用例(sample)编译

    前言 要使用DPDK的测试用例,必须先进行编译,以此记录编译的操作,方便日后查找 编译用例 设置环境变量,将DPDK的目录路径添加到编译代码中,RTE_SDK指示DPDK目录路径: export RT ...

  6. STM32 以太网学习

    STM32进行以太网通信,需要  了解一下内容: 硬件层:MAC控制器  和  PHY 和 变压器 . 软件层:网络协议栈,例如:lwip协议栈,RL-TCPnet协议栈,FreeRTOS-TCP协议 ...

  7. C语言入门编程思维引导

    编程思维引导: C语言中 include<stdio.h>  称之为导包,导入写好的函数库,多个则依次写 #define N 3  意思是将N这个字母定义为数字3 当使用的时候就直接用 i ...

  8. 记2019年目标之一没有996的大数据分析BI实战历程

    本文略长,阅读大约需要10分钟. 懵懵懂懂的学习了python,然后一发不可收拾的爱上了python大数据分析,慢慢的走进了大数据的学堂,学习如何大数据挖掘,大数据分析,到BI系统建设使用. 大数据的 ...

  9. TXT文件转换成DataSet数据集

    /// <summary> /// TXT文件转换成DataSet数据集 /// </summary> /// <param name="FilePath&qu ...

  10. MySQL学习【第七篇索引管理及执行计划】

    一.索引介绍 1.什么是索引? 索引由如字典,目的就是为了更快寻找到要找的内容. 令搜索查询的数据更有目的性,从而提高数据检索的能力 2.索引类型介绍 1.BTREE: B+树索引 2.HASH: H ...