C++ :STL中vector扩容机制

vector是STL提供的动态数组,它会在内部空间不够用时动态的调整自身的大小,调整过程中会有大量的数据拷贝,为了减少数据拷贝的次数vector会在调整空间的时候尽量多申请一些空间,这些预留出的空间可以很大程度上减少拷贝的发生。

在windows环境中使用vs运行这段代码

#include<iostream>
#include<vector>
using namespace std;
void fun(vector<int>&vec){vec.push_back(1);cout<<&vec[0]<<vec.size()<<" "<<vec.capacity()<<endl;
}
int main(){vector<int>vec;for(int i=0;i<10;i++) fun();
}

打印内容依次为,首元素地址,已经使用的空间,容器的容量
在这里插入图片描述

首先看push_back源码,第一个拷贝版本,第二个移动版本,需要注意的是这里第二个不是万能引用而是右值引用,下面看emplace_back代码

void push_back(const _Ty& _Val) { // insert element at end, provide strong guaranteeemplace_back(_Val);}void push_back(_Ty&& _Val) { // insert by moving into element at end, provide strong guaranteeemplace_back(_STD move(_Val));}

emplace_back使用了可变参数,并且对参数使用了万能引用,从而使得可以在插入节点时调用对应的构造函数,比如:vector<pair<int,int>>vec;vec.emplace_back(1,2);这么做可以很大程度上简化代码的书写,同时还可以减少一次移动或是拷贝的过程,decltype(auto)这个没什么说的根据返回值推导返回值类型。
回归正题,执行emplace_back的时候会判断容量是否充足,size!=capacity的时候会直接加入元素,否则的话会调用_Emplace_reallocate函数进行扩容。

template <class... _Valty>decltype(auto) emplace_back(_Valty&&... _Val) {// insert by perfectly forwarding into element at end, provide strong guaranteeauto& _My_data   = _Mypair._Myval2;pointer& _Mylast = _My_data._Mylast;if (_Mylast != _My_data._Myend) {return _Emplace_back_with_unused_capacity(_STD forward<_Valty>(_Val)...);}_Ty& _Result = *_Emplace_reallocate(_Mylast, _STD forward<_Valty>(_Val)...);
#if _HAS_CXX17return _Result;
#else // ^^^ _HAS_CXX17 ^^^ // vvv !_HAS_CXX17 vvv(void) _Result;
#endif // _HAS_CXX17}

下面看扩容代码,代码有点长我直接在代码上加注释了

template <class... _Valty>pointer _Emplace_reallocate(const pointer _Whereptr, _Valty&&... _Val) {// reallocate and insert by perfectly forwarding _Val at _Whereptr_Alty& _Al        = _Getal();auto& _My_data    = _Mypair._Myval2;//使用的长度pointer& _Myfirst = _My_data._Myfirst;//容器开始的位置pointer& _Mylast  = _My_data._Mylast;//容器末尾_STL_INTERNAL_CHECK(_Mylast == _My_data._Myend); // check that we have no unused capacityconst auto _Whereoff = static_cast<size_type>(_Whereptr - _Myfirst);//插入元素的位置,这个函数insert也有在用,所以插入的位置不一定在尾部const auto _Oldsize  = static_cast<size_type>(_Mylast - _Myfirst);//容器大小if (_Oldsize == max_size()) {//长度超过数组的最大长度时报错_Xlength();}const size_type _Newsize     = _Oldsize + 1;const size_type _Newcapacity = _Calculate_growth(_Newsize);//调用扩容函数const pointer _Newvec           = _Al.allocate(_Newcapacity);//申请新的空间const pointer _Constructed_last = _Newvec + _Whereoff + 1;//计算新的末尾pointer _Constructed_first      = _Constructed_last;_TRY_BEGIN_Alty_traits::construct(_Al, _Unfancy(_Newvec + _Whereoff), _STD forward<_Valty>(_Val)...);//在新的空间添加新的元素_Constructed_first = _Newvec + _Whereoff;//下面是根据插入元素的位置判断如何拷贝if (_Whereptr == _Mylast) { // at back, provide strong guarantee_Umove_if_noexcept(_Myfirst, _Mylast, _Newvec);//移动,不能移动时拷贝} else { // provide basic guarantee_Umove(_Myfirst, _Whereptr, _Newvec);_Constructed_first = _Newvec;_Umove(_Whereptr, _Mylast, _Newvec + _Whereoff + 1);}_CATCH_ALL//拷贝出现异常的时候释放对应的内容_Destroy(_Constructed_first, _Constructed_last);_Al.deallocate(_Newvec, _Newcapacity);_RERAISE;_CATCH_END_Change_array(_Newvec, _Newsize, _Newcapacity);//更新数组信息return _Newvec + _Whereoff;//偏移到新元素的地址}

下面看扩展策略,传入的_Newsize是_Oldsize + 1,_Max是容器最大容量一般是达不到的我试着输出了一下我的环境下是4611686018427387903,可以看到正常情况下新的容量是以前的容量的1.5倍(不同编译器的扩容策略不一样,g++中是2),当新的容量还不够的时候会转而按需分配。

size_type _Calculate_growth(const size_type _Newsize) const {// given _Oldcapacity and _Newsize, calculate geometric growthconst size_type _Oldcapacity = capacity();const auto _Max              = max_size();if (_Oldcapacity > _Max - _Oldcapacity / 2) {return _Max; // geometric growth would overflow}const size_type _Geometric = _Oldcapacity + _Oldcapacity / 2;if (_Geometric < _Newsize) {return _Newsize; // geometric growth would be insufficient}return _Geometric; // geometric growth is sufficient}

需要注意的是resize申请机制是按需分配的,当新的容量大于旧的时候会更新容量大小,当新的大小大于旧的容量的时候只会删除多余的元素而不会进行拷贝,数组容量不变不会释放数组空间但是多余的元素会被析构掉,详情运行下面这段代码。

#include<iostream>
#include<vector>
using namespace std;class A {
public:A(){}int* a = new int(1);~A() { cout << this << "析构"<<endl; }
};void fun(vector<A>& vec) {vec.push_back(A());cout << &vec[0] << " " << vec.size() << " " << vec.capacity() << endl;
}
int main() {vector<A>vec;for (int i = 0; i < 10; i++) fun(vec);vec.resize(5);cout << vec.capacity()<<endl;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/571799.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPF —— ContextMenu右键菜单 Canvas控件详解

ContextMenu右键菜单的实例 ​​​​​​​WPF中的右键菜单主要是通过ContextMenu来实现&#xff0c; 在控件中使用ContextMenu 直接在控件的ContextMenu属性中关联即可。 <Label Content"右键弹出内容菜单" FontSize"20" Width"200" Heig…

Redis面试题-缓存穿透,缓存击穿,缓存雪崩

1、穿透: 两边都不存在&#xff08;皇帝的新装&#xff09; &#xff08;黑名单&#xff09; &#xff08;布隆过滤器&#xff09; 解释&#xff1a;请求的数据既不在Redis中也不在数据库中&#xff0c;这时我们创建一个黑名单来存储该数据&#xff0c;下次再有类似的请求进来…

Go第三方框架--gin框架(二)

4. gin框架源码–Engine引擎和压缩前缀树的建立 讲了这么多 到标题4才开始介绍源码&#xff0c;主要原因还是想先在头脑中构建起 一个大体的框架 然后再填肉 这样不容易得脑血栓。标题四主要涉及标题2.3的步骤一 也就是 标题2.3中的 粗线框中的内容 4.1 Engine 引擎的建立 见…

MySQL语句(补充)

目录 一、子查询 1.1.select 语句 1.1.1.相同表查询 1.1.2.多表查询 1.1.3.NOT 1.1.4. insert 1.1.5. update 1.1.6.delete 1.1.7.exists 1.1.8.as别名 二、MySql视图 2.1.视图与表的区别和联系 2.2.建立视图 2.3.修改视图表数据 三、NULL值 四、连接查询 4…

Android Studio详细安装教程及入门测试

Android Studio 是 Android 开发人员必不可少的工具。 它可以帮助开发者快速、高效地开发高质量的 Android 应用。 这里写目录标题 一、Android Studio1.1 Android Studio主要功能1.2 Android应用 二、Android Studio下载三、Android Studio安装四、SDK工具包下载五、新建测试…

以实践助力《银行保险机构数据安全管理办法》规范落地

日前&#xff0c;金融监管总局网站显示&#xff0c;为规范银行业保险业数据处理活动&#xff0c;保障数据安全&#xff0c;促进数据合理开发利用&#xff0c;金融监管总局起草了《银行保险机构数据安全管理办法&#xff08;征求意见稿&#xff09;》&#xff08;下称《办法》&a…

【C语言】strcmp 的使⽤和模拟实现

前言 这篇文章将要带我们去实现模拟一个strcmp函数 首先我们要知道strcmp函数的定义 strcmp()定义和用法 我们先看一下strcmp在cplusplus网站中的定义 链接: link int strcmp ( const char * str1, const char * str2 );比较两个字符串将 C 字符串 str1 与 C 字符串 str2 …

【物联网开源平台】tingsboard安装与编译

别看这篇了&#xff0c;这篇就当我的一个记录&#xff0c;我有空我再写过一篇&#xff0c;编译的时候出现了一个错误&#xff0c;然后我针对那一个错误执行了一个命令&#xff0c;出现了绿色的succes,我就以为整个tingsboard项目编译成功了&#xff0c;后面发现的时候&#xff…

Python中lambda函数使用方法

在Python中&#xff0c;lambda 关键字用于创建匿名函数&#xff08;无名函数&#xff09;&#xff0c;这些函数的特点是简洁、一次性使用&#xff0c;并且通常用于只需要一行表达式的简单场景。下面是lambda函数的基本结构和使用方法&#xff1a; 基本语法&#xff1a; lambd…

JRT菜单

上一章搭建了登录界面的雏形和抽取了登录接口。给多组使用登录和菜单功能提供预留&#xff0c;做到不强行入侵别人业务。任何产品只需要按自己表实现登录接口后配置到容器即可共用登录界面和菜单部分。最后自己的用户关联到JRT角色表即可。 登录效果 这次构建菜单体系 首先用…

A Review on Influence Dissemination in Social Networks

Abstract 影响力传播研究是社交网络信息传播的关键问题。由于影响力分析在营销、广告、个性化推荐、舆情监测等方面的现实意义&#xff0c;研究人员从不同角度研究了该问题并提出了解决方案。在本文中&#xff0c;我们回顾了社交网络中的影响力传播&#xff0c;并得出结论&…

串口IAP介绍

一、STM32编程方式 &#xff08;1&#xff09;在线编程&#xff08;ICP&#xff0c;in circuit programming&#xff09; 系统存储器&#xff1a;留给ST写启动程序代码&#xff0c;启动程序代码通过串口1接口实现对闪存存储器的编程。 &#xff08;2&#xff09;在程序中编程…