vector作为实际项目用的非常频繁的容器,你了解它的内部实现吗?
比如vector<int>::itertor iter
1、iter到底是什么呢?
2、push_back到底发生什么了?
3、erase或者clear的时候内存有没有被释放?
如果这些问题你已经知道,可以不用看这篇文章了。
本文参考的版本是SGI for GCC。
大家都知道STL是泛型编程的典范,vector当然也是,有码有真相:
template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class vector : protected _Vector_base<_Tp, _Alloc>
{
//此处省略N多字
}
可以看出_Tp是value_type,而后面还有默认末班类型_Alloc,就是之前分析的内存池,如果不知道,请翻阅jjhou的书,或者参考 STL对空间配置的设计哲学-《STL源码剖析笔记》。
第一个问题,很简单,从源码就可以看到:
typedef const value_type* const_pointer;
typedef value_type* iterator;
typedef const value_type* const_iterator;
原来iterator就是value_type*啊,比如声明了vector<int>::itertor iter,这个iter其实就是int*。
2、push_back发生了什么呢?
当然vector在构造时,会预先分配写内存:
当然要看push_back的实现了:
void push_back(const _Tp& __x) {
if (_M_finish != _M_end_of_storage) {
construct(_M_finish, __x);
++_M_finish;
}
else
_M_insert_aux(end(), __x);
}
_M_finish与_M_end_of_storage指的是神马嘛?_M_finish是当前元素的最后位置,_M_end_of_storage是当前可用内存的最大处。
如果当前还有可用内存,那就直接用placement new构造元素(不知道的google placement new,其实很简单就是在给定内存上构造元素);
否则,就_M_insert_aux,这个函数体有点小复杂,但是是重点(插一句:古语有言:莫将容易得,却坐等闲看。):
记住我们要往最后一个实际当前无效的内存上去插入元素,因为end()肯定是无效啦。
好,现在看下代码注释版:
template <class _Tp, class _Alloc>
void
vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)
{
if (_M_finish != _M_end_of_storage) { //如果当前内存池还有内存
construct(_M_finish, *(_M_finish - 1)); //placement new一个出来
++_M_finish;
_Tp __x_copy = __x;
copy_backward(__position, _M_finish - 2, _M_finish - 1); //增大__position
*__position = __x_copy;//拷贝值上去
}
else {
const size_type __old_size = size(); //获得当前内存量
//这个有个东东要注意,开始插入第一个元素时,size为1;之后都是2倍扩张,oh!vector内存扩展的秘密就在这里
const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
iterator __new_start = _M_allocate(__len);//其实就是malloc分配 ,当然内部分配失败会handle的,这里不需要管
iterator __new_finish = __new_start;
__STL_TRY {//try的意思
// 下面就是内存从(_M_start, __position)的值复制到(__new_start, __new_start+(__position-_M_start))
// 谁让老家地方不够,现在可好, 要全部转移到高大上的新家了
__new_finish = uninitialized_copy(_M_start, __position, __new_start);
// 此时再在新家的下一个可用位置放下新成员__X
construct(__new_finish, __x);
// 更新以下各可用位置
++__new_finish;
// 这一步怎么来的,因为可能 (__position, _M_finish)之间还有成员,所以一并挪到新家
__new_finish = uninitialized_copy(__position, _M_finish, __new_finish);
}
// 这里是如果try失败,就回滚,释放分配的内存空间
__STL_UNWIND((destroy(__new_start,__new_finish),
_M_deallocate(__new_start,__len)));
// 搬进新家,老家就拆了吧,可用继续用作其他开发(比如XXX)
destroy(begin(), end());
_M_deallocate(_M_start, _M_end_of_storage - _M_start);
// 更新当前的start,finish,storage
_M_start = __new_start;
_M_finish = __new_finish;
_M_end_of_storage = __new_start + __len;
}
}
嗯嗯,就是酱紫,第二个问题搞明白了吧?如果没有,请打开jjhou的书狂看几遍。
3、erase不就是移除元素吗,要知道作为序列式容器 随机移除是O(N)的复杂度,因为要移位,这个原理大家应该都知道,但是被释放的内存哪里去了?
iterator erase(iterator __position) {
if (__position + 1 != end())//如果不是最后一个元素
//就把 (__position + 1, _M_finish)上的元素挪到 (__position, (_M_finish-__position + 1))上,
// 其实效率更高的是,memmove,STL内部当然很很聪明啦,如果型别是POD就会直接memmove,否则才酱紫挪
copy(__position + 1, _M_finish, __position);
// 更新finish位置
--_M_finish;
// 释放 _M_finish到哪里呢? 前面其实已经叙述过,如果从内存池分配的就还给内存池;否则才还给OS
destroy(_M_finish);
return __position;
}
clear就是重复调用了erase,原理是类似的。
好了,自此三个问题回答完毕,有木有很简单。