C++源码剖析——vector和array
前言:之前看过侯老师的《STL源码剖析》但是那已经是多年以前的,现在工作中有时候查问题和崩溃都需要了解实际工作中使用到的STL的实现。因此计划把STL的源码再过一遍。
摘要:本文描述了llvm中libcxx的std::vector的实现。
关键字:vector
其他:参考代码LLVM-libcxx
vector是标准库中的连续存储的容器,也就是容器中说任意两个索引上相邻的元素的地址也是相邻的,可以通过索引随机访问。vector中的元素默认是通过堆内存管理的,在进行空间分配时一般会比时机需求的空间要大,即capacity_size,这样能够避免在插入元素时频繁申请内存导致的性能问题(如果频繁申请内存导致页置换的话还是很耗时的)。
1 vector
先看下容器的定义,和其他容器一样都是一个模板类。_Tp就是类型,而_Allocator是进行内存管理的分配器,默认分配器就是通过::operator new和::operator delete申请和释放内存的。
template <class _Tp, class _Allocator /* = allocator<_Tp> */>
class _LIBCPP_TEMPLATE_VIS vector
vector的内存布局比较简单如下图,有三个指针分别指向了对应的开始地址,已使用部分的尾地址,申请到的内存的尾地址,[__begin_, __end_)之间是已经使用的内存部分,[__end_, __end_cap_)是申请了但是未使用的部分(保留这一部分是为了避免插入元素时频繁allocate而可能出现的性能问题。)

private:pointer __begin_ = nullptr;pointer __end_ = nullptr;__compressed_pair<pointer, allocator_type> __end_cap_ =__compressed_pair<pointer, allocator_type>(nullptr, __default_init_tag());
构造和销毁
vector的构造比较简单,就是先通过allocator申请一块内存,然后通过for循环逐个构造对象。构造时通过for循环实现,由于没有利用CPU的一些SMID指令的优化,必然效率不是很好。
vector(size_type __n, const value_type& __x, const allocator_type& __a) : __end_cap_(nullptr, __a){std::__debug_db_insert_c(this);if (__n > 0){__vallocate(__n);__construct_at_end(__n, __x);}
}vector<_Tp, _Allocator>::__construct_at_end(size_type __n, const_reference __x){_ConstructTransaction __tx(*this, __n);const_pointer __new_end = __tx.__new_end_;for (pointer __pos = __tx.__pos_; __pos != __new_end; __tx.__pos_ = ++__pos) {__alloc_traits::construct(this->__alloc(), std::__to_address(__pos), __x);}
}
销毁就比较直接,通过一个包装器__destroy_vector,先clear再调用deallocate释放内存。
__vec_.__clear();
__alloc_traits::deallocate(__vec_.__alloc(), __vec_.__begin_, __vec_.capacity());;
clear
clear函数只会针对析构容器中的每一个函数并不会释放当前容器中的内存。因此在进行容器释放时需要注意,如果期望释放内存的话可以通过vector().swap(vec)的方式或者在调用clear之后调用shrink_to_fit 调整内存大小。
void clear() _NOEXCEPT{size_type __old_size = size();__clear();__annotate_shrink(__old_size); //看源码里面什么也不会做std::__debug_db_invalidate_all(this);
}void __clear() _NOEXCEPT {__base_destruct_at_end(this->__begin_);}
void __base_destruct_at_end(pointer __new_last) _NOEXCEPT {pointer __soon_to_be_end = this->__end_;while (__new_last != __soon_to_be_end) //依然是一个完整的循环析构__alloc_traits::destroy(__alloc(), std::__to_address(--__soon_to_be_end));this->__end_ = __new_last;
}
push_back
push_back时,如果当前有足够的的大小则会在尾部构建一个对象,扩容的大小是按照现有大小的2倍来,即std::min(max_size(), std::max(current_cap + 1, 2 * current_cap)),简单的理解就是在条件允许的情况下扩容2倍。
void vector<_Tp, _Allocator>::__push_back_slow_path(_Up&& __x){allocator_type& __a = this->__alloc();//__split_buffer就是一个包装器__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), size(), __a);// __v.push_back(std::forward<_Up>(__x));__alloc_traits::construct(__a, std::__to_address(__v.__end_), std::forward<_Up>(__x));__v.__end_++;__swap_out_circular_buffer(__v);//这个函数没有干什么就是将__v中的size设置给当前的vector
}
emplace_back
emplace_back和push_back基本相同都是向容器中插入元素,如果对于插入vector::value_type类型的对象二者是没有区别的,push_back也实现了右值的重载,不存在push_back对于右值会多次拷贝的情况。主要的区别是emplace_back通过可变参数模板将参数直接传递给了构建器也就意味着同样的代码emplace_back直接在对应的内存上构建对象,而相比之下push_back是先构建再拷贝。
void vector<_Tp, _Allocator>::emplace_back(_Args&&... __args)
resize
resize的实现比较直接,内存小了就扩容,大了就析构但是并不释放内存。
void vector<_Tp, _Allocator>::resize(size_type __sz, const_reference __x){size_type __cs = size();if (__cs < __sz)this->__append(__sz - __cs, __x);else if (__cs > __sz)this->__destruct_at_end(this->__begin_ + __sz);//只会析构对象,不会释放内存
}
shrink_to_fit
vector<bool, _Allocator>::shrink_to_fit() _NOEXCEPT{if (__external_cap_to_internal(size()) > __cap()){vector(*this, allocator_type(__alloc())).swap(*this);}
}
vector<bool>基本上被建议放弃使用了,所以就不深入了。
2 array
array的实现比较简单就是一个简单的栈数组的包装器。就不详细描述了。
template <class _Tp, size_t _Size>
struct _LIBCPP_TEMPLATE_VIS array
{// types:typedef array __self;typedef _Tp value_type;typedef value_type& reference;typedef const value_type& const_reference;typedef value_type* iterator;typedef const value_type* const_iterator;typedef value_type* pointer;typedef const value_type* const_pointer;typedef size_t size_type;typedef ptrdiff_t difference_type;typedef _VSTD::reverse_iterator<iterator> reverse_iterator;typedef _VSTD::reverse_iterator<const_iterator> const_reverse_iterator;_Tp __elems_[_Size];
};
3 参考文献
- stackoverflow——push_back vs emplace_back
- Proposed Wording for Placement Insert
相关文章:
C++源码剖析——vector和array
前言:之前看过侯老师的《STL源码剖析》但是那已经是多年以前的,现在工作中有时候查问题和崩溃都需要了解实际工作中使用到的STL的实现。因此计划把STL的源码再过一遍。 摘要:本文描述了llvm中libcxx的std::vector的实现。 关键字&…...
学习linux编程(一)
本文导航一. Linux基础知识杂记0. terminal操作快捷键等1. 为什么vfork的子进程里用return,整个程序会挂掉,而且exit不会(zz)2. 进程内存管理详解3. 关于堆和自由存储区概念的区别4. cache和buffer的区别5. C实现线程池6. 静态函数和虚函数的区别7. C里是…...
pt-query-digest_详细使用方法
pt-query-digest_详细使用方法1. pt介绍1.1. 说明1.2. 安装2 语法选项2.1 所有参数2.2 常见参数2.3 事件和属性2.4 分组2.5 过滤2.6 排序2.7 输出选项2.8 DSN(数据源)选项3. 慢日志3.1 事件属性3.2 分析报告3.2.1 第一部分:总体概况说明3.2.2 第二部分:查…...
基于MATLAB编程的萤火虫FA优化BP神经网络的回归分析
目录 BP神经网络的原理 BP神经网络的定义 BP神经网络的基本结构 BP神经网络的神经元 BP神经网络激活函数及公式 SVM应用实例,基于fa-svm分类预测 代码 结果分析 展望 BP神经网络的原理 BP神经网络的定义 人工神经网络无需事先确定输入输出之间映射关系的数学方程,仅通过…...
leetcode 消失的数字(面试题)
题目 数组nums包含从0到n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗? 注意:本题相对书上原题稍作改动 示例 1: 输入:[3,0,1] 输出:2 示例 2: 输入&…...
Spring入门篇6 --- AOP
目录1.核心概念AOP(Aspect Oriented Programming)面向切面编程:一种编程范式,指导开发者如何组织程序结构作用:在不惊动原始设计的基础上为其进行功能增强。连接点(JoinPoint):程序执行过程中的任意位置切入点(PointCut)ÿ…...
linux 配置java环境
1、上传jdk包到/usr/local/java目录下 2、解压jdk的tar包 tar -zxvf jdk-8u291-linux-x64.tar.gz 3、添加配置(环境变量) 注意:JAVA_HOME值为实际jdk路径 打开配置文件 vi /etc/profile 最下面添加: #set java environment JAVA_HOME/usr/…...
分布式事务基础入门
分布式事务基础入门 什么是分布式事务 什么是分布式事务? 首先理解什么是本地事务? 平常我们在程序中通过spring去控制事务是利用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库来控制事务࿰…...
白盒测试究竟怎么做
大家好,我是洋子 在进行日常测试的时候,我们大部分时间花在手动的功能测试上,功能测试又可称为手工测试,官方一点的学名叫黑盒测试,当然作为测试工程师,我们一般俗称点点点 黑盒测试是一种软件测试方法&am…...
EEG微状态的功能意义
导读大脑的瞬时全局功能状态反映在其电场结构上。聚类分析方法一致地提取了四种头表面脑电场结构,这些结构能够最佳地解释自发EEG记录中随时间变化的差异。这四种结构被称为EEG微状态A、B、C和D类,分别与言语/语音、视觉、主观感受-自主加工和注意力重定…...
Python3 - Flask+swift实现单点登录
基于 Flask 和 Redis 实现单设备登录的服务端代码和客户端swift、oc代码: Python flask 实现服务端 from flask import Flask, jsonify, request from redis import Redisapp Flask(__name__) redis_db Redis()# 用户登录接口,验证用户名和密码&#…...
HTML URL
文章目录HTML URLURL - 统一资源定位器常见的 URL SchemeURL 字符编码URL 编码实例HTML URL URL 是一个网页地址。 URL可以由字母组成,如"csdn.net",或互联网协议(IP)地址: 192.168.100.1。大多数人进入网站…...
带你了解ICCV、ECCV、CVPR三大国际会议
文章目录 前言 一、ICCV、ECCV、CVPR是什么? 1.ICCV 2.ECCV 3.CVPR 二、三大会链接及论文下载链接 前言 作为刚入门CV的新人,有必要记住计算机视觉方面的三大顶级会议:ICCV,CVPR,ECCV,统称为ICE。 与其它学术领域不同,计算机科学使用会议而不是期刊作为发表研究成果的主…...
常用的一些代码
今天菜鸟涨工资了,到手估计有8000左右了,有点开心,本来想上一篇就把这篇写了的,但是发现还是分开写比较好! 文章目录自适应js禁止放大播放声音store的使用websocket封装echarts实现渐变swiper常用的陌生方法࿰…...
Python-df.pop()和np.array.shape()属性
1.df.pop() 删除某一列 可以使用这个来删除某一列(不能是多列),只有一个参数,就是列名,可以是str类型,函数返回的是被删除的列,df直接是删除后的df,不需要我们处理。 我们建模时&a…...
多线程并发编程笔记03(小滴课堂)---线程安全性
原子性操作: 这样一段代码。 我们输出一下: 我们发现它的结果和我们想的不太一样。 正常应该输出1000. 这是因为没有保证原子性。 所以我们来加上原子性: 这样就保证了我们的原子性。 接下来我们来细说说这个关键字: 我发现我…...
提升代码质量,使用插件对 java 代码进行扫描检查分析
目录前言一、使用maven-checkstyle-plugin插件1. maven-checkstyle-plugin 介绍2.引入依赖3.使用二、使用 idea 插件1.安装2.使用前言 很多时候我们的代码写的不规范,比如没缩进、参数间没空格、导入的包没用到没删除、方法很长没有进行拆分、 直接对方法参数进行了…...
如何用秒验提升用户体验和转换率?
手机号验证是移动应用开发中常见的需求,它可以用于用户注册、登录、身份认证等场景。目前,市场上主要的手机号验证方式是短信验证码,但这种方式存在一些问题,例如: 延迟:短信验证码需要等待运营商发送和用…...
【新】(2023Q2模拟题JAVA)华为OD机试 - 机器人活动区域
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:机器人活动区域 题目 现有一…...
2023软件测试面试真题宝典大汇总,没收藏的都后悔了
下边是我根据工作这几年来的面试经验,加上之前收集的资料,整理出来350道软件测试工程师 常考的面试题。字节跳动、阿里、腾讯、百度、快手、美团等大厂常考的面试题,在文章里面都有提到。 虽然这篇文章很长,但是绝对值得你点击一…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...
