STL容器真的好用吗

news2024/11/12 22:49:14

大家都在用的c++ STL就一定是完美无缺的吗?
本文一针见血的指出常见STL顺序容器vector的致命bug。

在Scott Meyers的《Effective C++》中,第一个条款明确指出,C++是一个语言联邦。
这体现在:
● C:C++继承了C语言的基础特性,包括区块、语句、预处理器、内置数据类型、数组、指针等。
● Object-Oriented C++:这是C++的面向对象编程部分,包括类(构造/析构函数)、封装、继承、多态、虚函数等。
● Template C++:C++的泛型编程部分,它引入了模板元编程(TMP)等新的编程范式。
● STL(Standard Template Library):C++的标准模板库,为容器、迭代器、算法以及函数对象等提供了高效的实现和规约。
我们可以看出STL容器在现代C++语言中的地位了,可以说是支撑柱一样的角色。

很多软件处理的数据单位以GB计算,这就意味着开辟内存时,数据结构往往有上千万个元素,而每个元素(结构体或基本数据类型)往往有数百个字节。那么用最基本vector或list容器就可以完美处理这么多的元素吗?
显然是不能的。首先list容器可以先排除,因为底层数据结构是双链表的list取值非常慢,这显然是程序无法忍受得了的。那么为了处理大批量的连续数据,很多人就会自然而然地选择vector容器,当然,很多C++开源库也是这样做的。
但是当vector容器要容纳并处理“数据结构往往有上千万个元素,而每个元素(结构体或基本数据类型)往往有数百个字节”这样的数据时,内存泄漏的恐怖bug就来了。明明逻辑没有错误,为什么会出现内存泄漏呢?这就不得不从vector的底层中说起。

vector的底层机制:可变大小数组

  1. 内存管理:
    ○ std::vector 使用连续的内存空间来存储元素。这意味着当你访问一个元素时,可以通过简单的指针运算快速定位到该元素(与数组类似)。
    ○ 当 vector 的大小超过其当前分配的内存空间时,它需要重新分配一块更大的内存空间,并将原有的元素复制到新的内存位置。这个过程被称为“扩容”或“重新分配”。
  2. 扩容策略:
    ○ 当 vector 需要扩容时,它通常会分配比当前所需大小更大的内存空间,以便在将来插入更多元素时减少重新分配的次数。这种策略被称为“预留容量”(reserve)或“过度分配”。
    ○ 具体的扩容策略(即每次扩容时增加多少容量)可能因不同的编译器和库实现而异。但通常,每次扩容时,vector 的大小会翻倍或按某个固定比例增长。
  3. 迭代器:
    ○ std::vector 提供了迭代器(iterator)来访问和修改其元素。迭代器是类似于指针的对象,可以用于遍历 vector 中的元素。
    ○ 由于 vector 使用连续的内存空间,其迭代器通常是简单的指针或指针的封装。这使得迭代器的操作(如递增、解引用等)非常高效。
  4. 插入和删除操作:
    ○ 在 vector 的尾部插入或删除元素是常数时间复杂度的操作,因为只需要调整 vector 的大小计数器并可能重新分配内存(对于插入操作)。
    ○ 在 vector 的中间或开头插入或删除元素则可能涉及元素的移动,因此具有线性时间复杂度。这是因为需要重新排列元素以保持连续的内存空间。
  5. 容量和大小:
    ○ std::vector 有两个重要的属性:size() 和 capacity()。size() 返回 vector 中实际元素的数量,而 capacity() 返回 vector 当前分配的内存空间可以容纳的元素数量。
    ○ 你可以使用 reserve() 成员函数来预留容量,以减少因扩容导致的重新分配次数。
    ○ 你也可以使用resize() 成员函数来更改容量大小。多就删除,少就开辟。
  6. 异常安全性:
    ○ std::vector 的操作通常是异常安全的。这意味着如果某个操作(如插入或删除元素)在执行过程中抛出异常,vector 的状态将保持不变(即回滚到操作开始前的状态)。
    vector 是使用 3 个迭代器来表示的:
    其中statrt指向vector 容器对象的起始字节位置;
    finish指向当前最后一个元素的末尾字节
    end_of指向整个 vector 容器所占用内存空间的末尾字节。
    下图演示了迭代器分别指向的位置。
    在这里插入图片描述
    在这里插入图片描述
    在此基础上,将 3 个迭代器两两结合,还可以表达不同的含义,例如:
    start 和 finish 可以用来表示 vector 容器中目前已被使用的内存空间;
    finish 和 end_of可以用来表示 vector 容器目前空闲的内存空间;
    start和 end_of可以用表示 vector 容器的容量。

vector的对象是如何增长的
vector对象为了支持快速随机访问,其物理存储方式是连续存储的,又因为vector是动态大小的,所以这就涉及到了一个问题。如果当前的vector容器分配的存储空间空间已经满了,不能再添加新的元素,那么就需要重新分配一块内存空间,将原来的值复制过去并添加新的元素。但是如果每次添加都重新分配内存空间的话,vector的效率会非常的低。所以为了避免这种低效的方式,vector有自己的内存增长方式,需要注意的是,这种增长方式不同的标准库实现者策略是不同的。总的来说,vector和string通常会分配比新空间需求(新空间需求即:容器当前需要存进去多少元素)更大的内存空间。以 VS 为例,新增加的容量一般是原来的0.5倍。
和容量相关的成员函数如下:
在这里插入图片描述
容器实际能够容纳的元素个数通常大于或者等于当前的需求,也就是容器会有多余的空间,当你声明一个新的vector对象时,一般的预分配空间是32个元素的空间。使用shrink_to_fit()可以收回多余的空间,但是这依赖于标准库的实现者,实现者有权不收回多余的空间。
capacity(),表示容器在不重新分配内存空间的情况下,最多可以容纳多少元素。
reserve(),改变容器的capacity。需要注意的是,reserve(n)分配的内存空间是小于等于capacity的。且当n小于容器当前实际存储的元素个数时,reserve是不会起作用的。也就是说reserve的n只是一个参考,当n<size()时,reserve不会起作用;当n>size()时,capacity()>=n。
那么:
capacity()表示在不重新分配内存空间的情况下,最多可以容纳多少元素。
size()表示当前容纳了多少元素。
并且:
resize()改变的是size()的大小,但是如果resize(n),n大于capacity(),就会改变capacity()。也就是,多就删除,少就开辟。
reserve()改变的是capacity()的大小,reserve(n),n<=capacity()。reserve()需要在声明vector对象后使用。如果中途使用,会带来意想不到的内存bug。
这就是,中途更改vector空间,一般用resize()。
当 vector 的大小和容量相等(size()==capacity())也就是满载时,如果再向其添加元素,那么 vector 就需要扩容。vector 容器扩容的过程需要经历以下 3 步:
完全弃用现有的内存空间,重新申请更大的内存空间;
将旧内存空间中的数据,按原有顺序移动到新的内存空间中;
最后将旧的内存空间释放。
但是,vector 容器在进行扩容后,与其相关的指针、引用以及迭代器有可能会失效。
由此可见,vector 扩容是非常耗时的。为了降低再次分配内存空间时的成本,每次扩容时 vector 都会申请比用户需求量更多的内存空间(这也就是 vector 容量的由来,即 capacity()>=size()),以便后期使用。

vector在处理大数据量时的缺陷
vector 在处理大数据量时的缺陷或限制,通常与内存管理、性能开销和数据局部性有关。即:
内存碎片:当 vector 需要扩容时,它通常会分配一块新的、更大的内存区域,并将旧数据复制过去。这可能会导致内存碎片,因为被释放的旧内存区域可能无法被有效地重新利用,特别是在内存分配和释放操作非常频繁的情况下。
性能开销:扩容操作(即分配新内存和复制数据)是昂贵的,特别是当 vector 包含大量数据时。这可能导致添加新元素的开销显著增加,特别是在实时或性能敏感的应用程序中。
数据局部性:vector 将元素连续存储在内存中,这有助于缓存局部性(cache locality)。然而,当 vector 扩容时,元素可能会被移动到新的内存地址,这可能会破坏缓存局部性,导致缓存未命中(cache misses)增加,进而降低性能。
内存占用:vector 在扩容时通常会分配比当前所需更多的内存作为备用。这可能导致不必要的内存占用,特别是在处理大数据量时。虽然这有助于减少扩容操作的频率,但在某些内存很宝贵的嵌入式系统下,这会很严重,直接导致内存崩了。
迭代器失效:vector 扩容时,指向其元素的迭代器可能会失效。这可能会使在遍历过程中修改 vector 的操作变得复杂和容易出错。

如何解决这一问题
为了克服这些缺陷,使用自定义内存管理。
简单的说就是,对所有代码中的vector容器,用C语言中的malloc、calloc、free或者C++中的new、new[]、delete、delete[],进行了重写。你也可以看到很多优秀稳定的开源库也都使用C/C++的这种基本内存管理模型,比如FFmpeg。简单、方便、内存可控,这就是解决方案。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

零基础学习Python(八)—— time模块、request模块、数据分析和自动化办公相关模块、jieba模块、文件操作和os相关模块的简单介绍

1. time模块 time()&#xff1a;获取当前时间戳&#xff0c;是一个数字 localtime()&#xff1a;返回一个time.struct_time对象&#xff0c;里面有年月日时分秒&#xff0c;还有星期几&#xff08;0表示星期一&#xff09;和今年的第几天 import timeprint(time.time()) pri…

我又被Spring的事务坑了,用户兑奖之后,什么东西都没收到!!

没错&#xff0c;我又被事务坑了&#xff01; 即上次的mq发送消息之后&#xff0c;业务代码回滚&#xff0c;导致发了一条中奖消息给用户&#xff01;&#xff01;&#xff0c;这次又被spring的事务坑了 这次是这样的&#xff0c;一个兑奖接口进来&#xff0c;我们先改变了这…

输入一段文字,瞬间生成应用。Furion低代码平台与AI协同工作,展现出非凡的效率与精准。

引言&#xff1a;展示问题背景 在当今快速变化的商业环境中&#xff0c;企业面临着前所未有的挑战。随着数字化转型的加速&#xff0c;企业对应用开发的需求日益增加。然而&#xff0c;传统的应用开发流程通常需要耗费大量时间和资源&#xff0c;从需求分析、设计、编码、测试到…

概念科普|ChatGPT是什么

一、引言 在人工智能的迅猛发展中&#xff0c;ChatGPT作为前沿技术的代表&#xff0c;其原理和运作方式成为了研究和讨论的热点。 常有观点将ChatGPT比喻为一种高级词语接龙&#xff0c;通过海量数据的学习与训练&#xff0c;机器能够根据给定的文本内容续写出下文&#xff0…

小米商业营销陈高铭:品牌应该多方整合,关注高质量营销 | SMARTIES CHINA 2024终审报道②

小米互联网业务部商业营销品牌总经理 陈高铭 近日&#xff0c;SMARTIES CHINA 2024终审活动在苏州音昱水中天落下帷幕。来自各行业的40位品牌广告主代表&#xff0c;历时两天时间&#xff0c;通过紧张的评审和精彩的讨论&#xff0c;从178个优秀入围案例中评选出了每个类别的金…

数据分析-螺旋环状气泡图

1 原理 采用阿基米德螺线原理&#xff0c;即以一个点匀速离开一个固定点的同时又以固定的角速度绕该固定点转动而产生的轨迹。具体原理见&#xff1a;阿基米德螺线。坐标轴公式为&#xff1a; 其中x为横坐标&#xff0c;y为纵坐标&#xff0c;r为离中心点的半径&#xff0c;为坐…

茶叶商家开店拓客线上发展增长生意

部分地区的人群酷爱喝茶&#xff0c;其他地区也有大量购茶者&#xff0c;其爱好者对茶叶的种类、年份季节口感度、价格等有着较高要求&#xff0c;花茶、绿茶、红茶、白茶、龙井、碧螺春、乌龙茶等&#xff0c;国内国外庞大市场&#xff0c;不能仅局限于本地附近客户&#xff0…

【ArcGIS】栅格计算器原理及案例介绍

ArcGIS&#xff1a;栅格计算器原理及案例介绍 栅格计算器&#xff08;Raster Calculator&#xff09;原理介绍案例案例1&#xff1a;计算栅格数据平均值 参考 栅格计算器&#xff08;Raster Calculator&#xff09;原理介绍 描述&#xff1a;在类似计算器的界面中&#xff0c;…

跨境独立站支付收款常见问题排雷篇1.0丨出海笔记

最近小伙伴们在社群讨论挺多关于独立站支付问题的&#xff0c;鉴于不少朋友刚接触独立站&#xff0c;我整理了一些独立站支付相关的问题和解决方案&#xff0c;供大家参考&#xff0c;百度网上一堆媒体的那些软文大家就别看了&#xff0c;都是软广或者抄来抄去&#xff0c;让大…

华为 昇腾 310P 系列 AI 处理器支持 140Tops 的 AI 算力。

1、产品简介 模组是基于昇腾 310P 系列 AI 处理器设计而成&#xff0c;可实现图像、视频等多种数据分析 与推理计算。超强的视频编解码能力以及支持 140Tops 的 AI 算力。在边缘侧及端侧的嵌入式计算 领域&#xff0c;有着极高的性价比&#xff0c;具有超强算力、 超高能效、…

2024最新版mysql数据库表的查询操作-总结

序言 1、MySQL表操作(创建表&#xff0c;查询表结构&#xff0c;更改表字段等)&#xff0c; 2、MySQL的数据类型(CHAR、VARCHAR、BLOB,等)&#xff0c; 本节比较重要&#xff0c;对数据表数据进行查询操作&#xff0c;其中可能大家不熟悉的就对于INNER JOIN(内连接)、LEFT JOIN…

产业互联网新星闪耀,“太行云商”引领传统产业数字化革命

产业互联网新星升起 晋城&#xff0c;作为我国能源及相关产业的重要基地&#xff0c;一直面临着信息协同不畅、存货供需失衡、资金融通困难等诸多挑战。 2023年&#xff0c;为了应对这些难题&#xff0c;晋城市政府携手AMT企源&#xff0c;成立了太行云商科技有限公司&#x…

【生日视频制作】蓝色飞机机身AE模板修改文字软件生成器教程特效素材【AE模板】

生日视频制作教程蓝色飞机机身AE模板修改文字特效广告生成神器素材祝福玩法AE模板工程 AE模板套用改图文教程↓↓&#xff1a; 怎么如何做的【生日视频制作】蓝色飞机机身AE模板修改文字软件生成器教程特效素材【AE模板】 生日视频制作步骤&#xff1a; 下载AE模板 安装AE软件…

串口调试助手+串口打印配置

目录 一、使用CUBE_MX配置串口 二、KEIL5配置 1.打开usart.c文件 2.打开main.c文件 在main函数中&#xff1a; 一、使用CUBE_MX配置串口 二、KEIL5配置 1.打开usart.c文件 UART_HandleTypeDef huart1;void MX_USART1_UART_Init(void) {/* USER CODE BEGIN USART1_Init 0 …

【Proteus51单片机仿真】YL-69湿度检测和继电器电机自启动

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于AT89C51单片机&#xff0c;湿度检测模块通过ADC0832数模转换器&#xff0c;将模拟信号转换为数字信号给单片机&#xff0c;然后通过LCD1602显示屏显示出当前的湿度值&#xff0c;并判断当前湿度值是…

大模型的第一个杀手级应用场景出来了

大家终于都意识到大模型首先改变的是软件行业自己&#xff0c;而软件的根基是代码生成。代码生成第一波就是AI辅助开发&#xff0c;这个会是大模型第一个杀手级应用。大家苦苦逼问自己的大模型杀手级应用&#xff0c;为什么会是辅助编程&#xff0c;这里说下什么&#xff1a; 必…

数据集 Total-Text 文本检测 >> DataBall

开源数据集 Total-Text 文本检测 深度学习 人工智能 Total-Text 是一个文本检测数据集&#xff0c;由 1,555 张图像组成&#xff0c;具有多种文本类型&#xff0c;包括水平、多向和弯曲文本实例。训练分割和测试分割分别有 1,255 张图像和 300 张图像。 article{CK2019, author…

时序预测 | Matlab实现PSO-CNN粒子群优化卷积神经网络时间序列预测

时序预测 | Matlab实现PSO-CNN粒子群优化卷积神经网络时间序列预测 目录 时序预测 | Matlab实现PSO-CNN粒子群优化卷积神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现PSO-CNN粒子群优化卷积神经网络时间序列预测&#xff08;完整源码和数…

新手教学系列——用Nginx将页面请求分发到不同后端模块

在当今的Web开发中,前后端分离架构已经成为主流,尤其是大型应用项目。前端可以通过Vue这样的框架来统一管理页面和用户交互,而后端则通常会拆分成多个微服务模块,以便应对不同业务需求和功能扩展。在这样的架构下,Nginx作为一个高效、灵活的Web服务器,能够帮助我们将前端…

打造自己的大模型LLMs!独家训练秘籍!

目录 前言大语言模型 Vs机器学习模型训练过程 步骤1&#xff1a;数据策划&#xff08;Data Curation)步骤2&#xff1a;格式化与预处理步骤3&#xff1a;训练模型步骤4&#xff1a;模型评估 LLM Leaderboard [LLM Leaderboard 2024](https://www.vellum.ai/llm-leaderboard)[O…