前面在介绍push_back函数的时候有说到placement new的用法.前面说的很简单.这几天处理一些其他的事情,直到昨天下午

才有时间看源码,顺便安静的看一下书. 其中我又看到了挂关于placement new的介绍,那么就在文章开始之前先说一下这个问题.

  placement new又称"定为new"(我想这纯粹是翻译过来的意思.),当在禁止使用Heap分配的时候,也就是声明对象的构造函数

为private的时候,我们不能调用构造函数去构造一个对象,这个时候就可以使用placement new. 前一段时间我在阅读sig stl源码的

时候也看到了stl容器对于placement new的使用.

  placement new 的作用是在一个特定的位置放置一个对象,所以不需要调用构造函数,但是却和构造函数的作用相同. 

需要注意的是placement new并不分配实际的存储区域, 而是返回一个指向已经分配好空间的指针.所以不要对其执行delete操作.

  但是确实创建了一个对象,如果想要销毁对象,那么需要调用嗯对象的析构函数.

       placement大多都是使用在容器技术中,而且是高级技术,也通常用来隐藏技术实现的细节,这里只做简单了解.

 

前面很多文章都是介绍stl_vector,这篇文章会介绍vector的resize函数,并作为结尾. 先看一下resize函数的源码:

  void resize(size_type __new_size, const _Tp& __x) {if (__new_size < size()) erase(begin() + __new_size, end());    // 擦除begin()+__new_size -> end()之间的元素elseinsert(end(), __new_size - size(), __x);}void resize(size_type __new_size) { resize(__new_size, _Tp()); }  // 这和上面一样,只不过是提供默认的参数.

1. 首先第一点很容易看得出,erase函数执行的是擦除工作,并不能分配内存.

2. insert在__new_size >= size()的时候会执行内存的重新分配.

 

先看一下erase的源码:

  iterator erase(iterator __first, iterator __last) {iterator __i = copy(__last, _M_finish, __first);destroy(__i, _M_finish);_M_finish = _M_finish - (__last - __first);return __first;}

 跟着copy的源码走下去,最终会看到最后的实现是:(__copy_trivial)

template <class _Tp>
inline _Tp*
__copy_trivial(const _Tp* __first, const _Tp* __last, _Tp* __result) {memmove(__result, __first, sizeof(_Tp) * (__last - __first));return __result + (__last - __first);
}

 其实上面的erase在resize函数中使用的时候是不会执行copy函数的.因为end() == _M_finish.所以只需要将

   begin() + __new_size -> _M_finish之间的元素都销毁.destory源码如下:

inline void _Destroy(_Tp* __pointer) {__pointer->~_Tp();  // 这里不是使用delete,而是调用元素对象本身的析构函数.
}

  找了很多层,执行很多跳转最后才找到上面的最终源码,为什么stl要这么麻烦? 因为stl对于容器的内存是使用placement new

  前面说过,所以需要调用对象本身的析构函数来完成工作.

 

3. 接下来,我们看看,如果__new_size > size()执行insert函数的情况.insert函数源码如下:

  void insert (iterator __pos, size_type __n, const _Tp& __x){ _M_fill_insert(__pos, __n, __x); }

  下面调用到_M_fill_insert函数,这个函数在前面的文章中有讲解过.不过当时只讲解了该函数的一部分. 本文来看看上半部分. 

template <class _Tp, class _Alloc>
void vector<_Tp, _Alloc>::_M_fill_insert(iterator __position, size_type __n, const _Tp& __x)
{if (__n != 0) {   // __new_size - size() > 0 if (size_type(_M_end_of_storage - _M_finish) >= __n) {_Tp __x_copy = __x;const size_type __elems_after = _M_finish - __position;iterator __old_finish = _M_finish;if (__elems_after > __n) {uninitialized_copy(_M_finish - __n, _M_finish, _M_finish);_M_finish += __n;copy_backward(__position, __old_finish - __n, __old_finish);fill(__position, __position + __n, __x_copy);}else {uninitialized_fill_n(_M_finish, __n - __elems_after, __x_copy);_M_finish += __n - __elems_after;uninitialized_copy(__position, __old_finish, _M_finish);_M_finish += __elems_after;fill(__position, __old_finish, __x_copy);}}

  在上面的注释中,只会调用_M_fill_insert函数的前部分,而后半部分的源码在前面讲解过了.

    情况又作为两种子情况划分:

      1. size_type(_M_end_of_storage - _M_finish) >   __n(__n = __new_size - size() )

      2. size_type(_M_end_of_storage - _M_finish) <= __n(__n = __new_size - size())

  

对于这两种情况该如何解释呢?如何去理解. _M_end_of_storage代表着存储能力,_M_finish代表着当前的已存储位置, size()

返回的不是容器的存储能力,而是当前已经存储的元素个数. 理解了这些,上面的两种情况就比较好理解了.  

  

  大的前提条件是:需要resize的新长度已经大于已经存储的长度.

    对于1情况: __new_size在_M_end_of_storage内. 也就是在存储能力内.

    对于2情况: 则不在存储能力范围内了,需要扩大存储能力,也就是扩大存储内存单元.

好吧,我们去看看STL源码是如何处理的.

 

对于情况1:

  if (size_type(_M_end_of_storage - _M_finish) >= __n) {_Tp __x_copy = __x;const size_type __elems_after = _M_finish - __position;iterator __old_finish = _M_finish;if (__elems_after > __n) {  // 这下面的语句不会执行,因为_M_finish = _position uninitialized_copy(_M_finish - __n, _M_finish, _M_finish);_M_finish += __n;copy_backward(__position, __old_finish - __n, __old_finish);fill(__position, __position + __n, __x_copy);}else {  // 从这里开始执行.uninitialized_fill_n(_M_finish, __n - __elems_after, __x_copy);_M_finish += __n - __elems_after;                    // _M_finish 向后移动__n-1uninitialized_copy(__position, __old_finish, _M_finish);   _M_finish += __elems_after;fill(__position, __old_finish, __x_copy);}}

  我们假设容器中存储的元素类型不是POD,所以追溯源码就可以找到这里:

template <class _ForwardIter, class _Size, class _Tp>
_ForwardIter
__uninitialized_fill_n_aux(_ForwardIter __first, _Size __n,const _Tp& __x, __false_type)
{_ForwardIter __cur = __first;__STL_TRY {for ( ; __n > 0; --__n, ++__cur)_Construct(&*__cur, __x);return __cur;}__STL_UNWIND(_Destroy(__first, __cur));
}

  很容易看得出,就是从_M_finish到后面的_n个位置都使用placement new初始化元素为__x.

  接着查看uninitialized_copy的源码可以发现并不会执行: 和前面一样,元素类型同样不是POD

template <class _InputIter, class _ForwardIter>
_ForwardIter
__uninitialized_copy_aux(_InputIter __first, _InputIter __last,_ForwardIter __result,__false_type)
{_ForwardIter __cur = __result;__STL_TRY {for ( ; __first != __last; ++__first, ++__cur)   // _position == old_position , 所以不会执行._Construct(&*__cur, *__first);return __cur;}__STL_UNWIND(_Destroy(__result, __cur));
}

  

   好吧,对于情况1的分析,已经很清楚了.下面看看情况2.

   const size_type __old_size = size();        const size_type __len = __old_size + max(__old_size, __n);  // 这里不是简单的扩张两倍.因为不知道new_size和old_size之间的关系.iterator __new_start = _M_allocate(__len);           // 重新申请内存iterator __new_finish = __new_start;              // 初始化__STL_TRY {__new_finish = uninitialized_copy(_M_start, __position, __new_start);  // 将原数组中_M_start -> _M_finish之间的元素复制到新数组中__new_finish = uninitialized_fill_n(__new_finish, __n, __x);       // 初始化_new_finish -> _new_finish + __n 之间的元素__new_finish= uninitialized_copy(__position, _M_finish, __new_finish);

  情况2执行的代码相比较情况1要少的多,尽管要执行新的内存分配. 在上面的源码注释中,我也注明了,这里不是简单的扩张为原来的两倍.

  为什么要这么做呢? 原因其实很简单,因为resize时候,__new_size和size()之间的关系是不知道的,有可能是三倍四倍,也有可能是二倍,

  或者说是介于这些数字之间,所以不应该用一个确切的数字来决定.

  不知道会不会有人问一个问题:  关于内存扩张_len的确定为什么和_M_end_of_storage没有关系了,就是为什么和存储能力没有关系了。

  而是和size()有关系.  额,答案是这个样子的.在前面分析两种情况的时候就说明了,情况2是已经不在存储范围内了,所以需要结合这些基本情况

  联系在一起考虑.

 

最后对于情况1,情况2都执行相同的源码:  

    destroy(_M_start, _M_finish);_M_deallocate(_M_start, _M_end_of_storage - _M_start);_M_start = __new_start;_M_finish = __new_finish;_M_end_of_storage = __new_start + __len;

  执行一些初始化和清理工作,收尾.

  

      到这里,关于resize函数的介绍就都结束了. 

 

 

  小结:

    STL中容器对于元素的存储在底层使用的都是数组,而实现数据结构都是使用_M_start,_M_finish,_M_end_of_storage.

    STL中的函数提供的通用性是很好的,而且源码的设计与数据结构的实现很精巧,同时也是很复杂的. 

 

 

 

 

 

 

 

  

 

 

 

 

  

 

 

转载于:https://www.cnblogs.com/respawn/archive/2012/08/03/2621125.html

C++ Standard Stl -- SGI STL源码学习笔记(07) stl_vector 与 一些问题的细化 3 resize函数剖析...相关推荐

  1. Apache log4j-1.2.17源码学习笔记

    (1)Apache log4j-1.2.17源码学习笔记 http://blog.csdn.net/zilong_zilong/article/details/78715500 (2)Apache l ...

  2. Java多线程之JUC包:Semaphore源码学习笔记

    若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5625536.html Semaphore是JUC ...

  3. RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?

    RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 文章目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目 ...

  4. Vuex 4源码学习笔记 - 通过Vuex源码学习E2E测试(十一)

    在上一篇笔记中:Vuex 4源码学习笔记 - 做好changelog更新日志很重要(十) 我们学到了通过conventional-changelog来生成项目的Changelog更新日志,通过更新日志 ...

  5. Vuex 4源码学习笔记 - Vuex是怎么与Vue结合?(三)

    在上一篇笔记中:Vuex源码学习笔记 - Vuex开发运行流程(二) 我们通过运行npm run dev命令来启动webpack,来开发Vuex,并在Vuex的createStore函数中添加了第一个 ...

  6. jquery源码学习笔记三:jQuery工厂剖析

    jquery源码学习笔记二:jQuery工厂 jquery源码学习笔记一:总体结构 上两篇说过,query的核心是一个jQuery工厂.其代码如下 function( window, noGlobal ...

  7. 雷神FFMpeg源码学习笔记

    雷神FFMpeg源码学习笔记 文章目录 雷神FFMpeg源码学习笔记 读取编码并依据编码初始化内容结构 每一帧的视频解码处理 读取编码并依据编码初始化内容结构 在开始编解码视频的时候首先第一步需要注册 ...

  8. PHP Yac cache 源码学习笔记

    YAC 源码学习笔记 本文地址 http://blog.csdn.net/fanhengguang_php/article/details/54863955 config.m4 检测系统共享内存支持情 ...

  9. Vuex 4源码学习笔记 - 通过dispatch一步步来掌握Vuex整个数据流(五)

    在上一篇笔记中:Vuex 4源码学习笔记 - Store 构造函数都干了什么(四) 我们通过查看Store 构造函数的源代码可以看到主要做了三件事情: 初始化一些内部变量以外 执行installMod ...

最新文章

  1. 03 在百度地图上定位到指定位置
  2. unity快速接入第三方sdk_直播美颜SDK实现需要具备哪些条件
  3. 在同一网段内运行同一命令_一高楼两层同时起火!同一天内,全国竟发生数起“高层火灾”!...
  4. java switch语句_Java 14:查看更新的switch语句
  5. python 无序列表中第k大元素_Python要求O(n)复杂度求无序列表中第K的大元素实例...
  6. 基于软件工程的Qt播放器探索(一) 概述
  7. 怎样破解QQ空间代码(转载)及最新收集2009年QQ空间皮肤代码大全 (http://www.enet.com.cn/article/2009/0812/A20090812519367.shtml)
  8. 生物信息数据格式:fasta格式
  9. 美国陪审团裁定福特向车祸遇难者家属赔偿17亿美元
  10. 大学生简单个人静态HTML网页设计作品 DIV布局个人介绍网页模板代码 DW学生个人网站制作成品下载 HTML5期末大作业
  11. PySide6 布局管理
  12. install ubuntu source code
  13. 红米1S刷机android7.1记录
  14. JAVA中将数据保存到EXCEL文件
  15. <2021SC@SDUSC>【Overload游戏引擎】OvCore源码模块分析(一)——APIComponent
  16. java怎么写游戏脚本,感悟分享
  17. 业务架构20多年,技术人员理解了吗?
  18. “四朵云”与“垂直生意”都需要大数据来背书
  19. windows 10 上传文件夹到 Linux服务器 压缩文件夹 到tar.gz格式
  20. 【纪中集训2019.3.25】芬威克树

热门文章

  1. 在Powershell中禁止执行脚本
  2. CIA公布了收集和存储美国公民信息的新规则
  3. 春风十里不如春城一聚:华平解决方案巡展走进昆明
  4. angular组件-特殊的瀑布流(原创)
  5. CentOS7 编译 Gradle
  6. 项目常用的PHP代码
  7. 高手进阶:/etc/profile环境变量配置解析
  8. 银屑病与肠道菌群(调研手稿一)
  9. 关于甲沟炎的一些办法
  10. 关于flink的setCommitOffsetsOnCheckpoints