《STL源码剖析》-- 序列式容器
参考文献:
《STL源码剖析》
https://blog.csdn.net/qq_38790716/article/details/85042557
..........................................................................................................
前言:
所谓的序列式容器,其中的元素都可序,但未必有序,C++本身提供了一个序列式容器array,STL 提供了vector, list, deque, srack, queue, priority-queue等
一. 使用reverse()函数提前设定容量大小
1.1 提前设定的原因-
对于vector和string,如果需要更多的内存空间,就会以类似realloc的思想来增长大小。vector容器支持随机访问,因此为了提高效率,它内部是使用动态数组的方式实现的
在通过reverse()函数来申请特定大小的内存空间时总是按指数边界来增大其内部缓冲区;当执行insert或push_back等增加元素的操作时,如果此时动态数组的内存不够用,就要重新分配内存,再把原数组的内容复制过来,所以其访问速度与一般的数组相比,只有发生重新分配内存时,其性能才会下降
因此如果需要对大量的数据进行push_back操作,应当使用reverse()函数提前设定其容量大小,否则会出现许多次内存分配,造成效率低下
1.2 提前设定的原因
- reverse成员函数允许开发者最小化必须进行的重新分配的次数,因而可以避免真分配的开销和迭代器、指针、引用失效
- 例如,下面这段代码:
1 vector<int> v; 2 for (int i = 1; i <= 1000; ++i) v.push_back(i);
在大多数STL实现中,这段代码在循环过程中将会导致2~10次的重新分配,然后我们把代码改用reverse:
1 vector<int> v; 2 v.reverse(1000); 3 for (int i = 1; i <= 1000; ++i) v.push_back(i);
1.3 使用reverse的情况
- 当知道有多少元素将最后出现在容器中时,就可以提前reverse适当数量的空间
- 保留可能需要的最大的空间,然后再添加完全部数据后,再修整掉多余的容量
二. 源码解析
vector 提供了许多constructors,其中一个允许我们指定空间大小与初值:
1 vector(size_type __n, const _Tp& __value, 2 const allocator_type& __a = allocator_type()) 3 : _Base(__n, __a) 4 { _M_finish = uninitialized_fill_n(_M_start, __n, __value); } 5 6 //上面 uninitialized_fill_n 这个函数,藏得很深在stl_uninitialized.h里面,实际该函数还是对fill函数的一次封装 7 //最终调用的是fill函数,在 stl_alogbase.h 这个头文件下面,如下, 8 //不过这个文件夹下面有很多fill函数,我想应该是uninitialized_fill_n的作用还包括型别判断,根据型别不同调用不同的fill函数 9 10 void fill(_ForwardIter __first, _ForwardIter __last, const _Tp& __value) { 11 __STL_REQUIRES(_ForwardIter, _Mutable_ForwardIterator); 12 for ( ; __first != __last; ++__first) 13 *__first = __value; 14 }
我们以 push_back() 函数将新元素插入vector尾端时,该函数首先检查是否还有备用空间,如果有则直接在备用空间上构造元素,并调整迭代器finish,使得vector变大。如果没有备用空间就扩充:
1 void push_back(const _Tp& __x) { 2 if (_M_finish != _M_end_of_storage) { 3 construct(_M_finish, __x); //直接placement构造元素,移动_M_finish指针 4 ++_M_finish; 5 } 6 else 7 _M_insert_aux(end(), __x); //重新构造整个vector并插入元素 8 } 9 //表示不懂为啥要重载一次push_back()。。。 10 void push_back() { 11 if (_M_finish != _M_end_of_storage) { 12 construct(_M_finish); 13 ++_M_finish; 14 } 15 else 16 _M_insert_aux(end()); 17 }
至于 _M_insert_aux 函数,
1 vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x) 2 { 3 if (_M_finish != _M_end_of_storage) //有备用空间 4 { 5 //在备用空间起始处构造一个元素,并以vector最后一个元素为其初始值 6 construct(_M_finish, *(_M_finish - 1)); 7 ++_M_finish; //调整水位 8 _Tp __x_copy = __x; 9 copy_backward(__position, _M_finish - 2, _M_finish - 1); 10 *__position = __x_copy; 11 } 12 else //无备用空间 13 { 14 const size_type __old_size = size(); 15 const size_type __len = __old_size != 0 ? 2 * __old_size : 1; 16 //以上配置原则,如果原大小为0,则配置一个元素大小 17 //如果原大小不为零,则配置原来大小的两倍 18 //前半段用来存放原始数据,后半段用来存放新数据 19 20 iterator __new_start = _M_allocate(__len); 21 iterator __new_finish = __new_start; 22 __STL_TRY 23 { 24 //原vector内容拷贝到新vector 25 __new_finish = uninitialized_copy(_M_start, __position, __new_start); 26 construct(__new_finish, __x); //为新元素设定初始x 27 ++__new_finish; //调整水位 28 //安插点的原内容也一起拷贝 29 __new_finish = uninitialized_copy(__position, _M_finish, __new_finish); 30 } 31 __STL_UNWIND((destroy(__new_start,__new_finish), 32 _M_deallocate(__new_start,__len))); 33 //销毁,释放原vector 34 destroy(begin(), end()); 35 _M_deallocate(_M_start, _M_end_of_storage - _M_start); 36 //调整迭代器,指向新的vector 37 _M_start = __new_start; 38 _M_finish = __new_finish; 39 _M_end_of_storage = __new_start + __len; 40 } 41 }
三. vector 的元素操作:pop_back, erase, clear, insert
3.1 ease() 函数作用是清除某一个元素,或者清除两个迭代器之间的所有元素,如下:
1 //清除某一个元素 2 iterator erase(iterator __position) 3 { 4 if (__position + 1 != end()) 5 copy(__position + 1, _M_finish, __position); 6 --_M_finish; 7 destroy(_M_finish); 8 return __position; 9 } 10 //清除迭代器__first 和 __last之间的所有元素 11 iterator erase(iterator __first, iterator __last) 12 { 13 iterator __i = copy(__last, _M_finish, __first); //将__last到_M_finish(最后一个元素的下一个cell)所有元素copy到从__first开始的地方 14 //返回_M_finish对应的cell,在copy之后所在位置的迭代器,赋值给__i 15 //然后销毁从__i到_M_finish的所有元素,并移动_M_finish到删除元素之后新元素序列的尾端的下一个cell 16 17 destroy(__i, _M_finish); _M_finish = _M_finish - (__last - __first); return __first; 18 }
第二个 erase 示意图如下:
3.2 insert函数是把元素插入到对应位置,该函数效率很低,特别是front插入,要移动所有元素退后一个位置,很花销时间,企业级数据尽量少用 vector 的 insert,以下是其源代码:
1 template <class _Tp, class _Alloc> 2 void vector<_Tp, _Alloc>::_M_fill_insert(iterator __position, size_type __n, 3 const _Tp& __x) 4 { 5 if (__n != 0) //防止不插入元素而造成资源耗费 6 { 7 //备用空间大小大于或者等于新增元素个数 8 if (size_type(_M_end_of_storage - _M_finish) >= __n) 9 { 10 _Tp __x_copy = __x; 11 // 12 const size_type __elems_after = _M_finish - __position; 13 iterator __old_finish = _M_finish; 14 //我的技术太渣,理解倒是理解了,就是不知道为啥这么写。。。。 15 if (__elems_after > __n) 16 { 17 uninitialized_copy(_M_finish - __n, _M_finish, _M_finish); 18 _M_finish += __n; 19 copy_backward(__position, __old_finish - __n, __old_finish); 20 fill(__position, __position + __n, __x_copy); 21 22 23 } 24 else 25 { 26 uninitialized_fill_n(_M_finish, __n - __elems_after, __x_copy); 27 _M_finish += __n - __elems_after; 28 uninitialized_copy(__position, __old_finish, _M_finish); 29 _M_finish += __elems_after; 30 fill(__position, __old_finish, __x_copy); 31 } 32 33 //不明白为什么要比较__elems_after 和 _n,不是能够全体移动并且直接插入要插入的元素吗?所以 34 //感觉特别复杂,个人觉得可以改成: 35 uninitialized_copy(__position, _M_finish, __position + n); 36 _M_finish += __n; 37 fill(__position, __position + __n, __x_copy); 38 } 39 else 40 { 41 //备用空间大小小于新增元素个数,必须配置额外内存,那就用到空间是配置器了 42 const size_type __old_size = size(); 43 //决定新的长度,为旧的长度的两倍,或者旧的长度+新的长度 44 const size_type __len = __old_size + max(__old_size, __n); 45 //开始调用空间配置器 46 iterator __new_start = _M_allocate(__len); 47 iterator __new_finish = __new_start; 48 __STL_TRY 49 { //移动元素 50 //首先移动要插入点位置之前的所有元素到新的vector 51 __new_finish = uninitialized_copy(_M_start, __position, __new_start); 52 //其次开始构造要插入的元素 53 __new_finish = uninitialized_fill_n(__new_finish, __n, __x); 54 //最后移动要插入点位置之后的所有元素到新的vector 55 __new_finish 56 = uninitialized_copy(__position, _M_finish, __new_finish); 57 } 58 __STL_UNWIND((destroy(__new_start,__new_finish); 59 //以下清除并且释放旧的vector 60 _M_deallocate(__new_start,__len))); 61 destroy(_M_start, _M_finish); 62 _M_deallocate(_M_start, _M_end_of_storage - _M_start); 63 //移动起始点和水位 64 _M_start = __new_start; 65 _M_finish = __new_finish; 66 _M_end_of_storage = __new_start + __len; 67 } 68 } 69 }