C++标准库中string的底层实现方式

对于C++中 std::string 的一些基本功能和用法,我们应该都很熟悉。但它底层到底是如何实现的呢? 其实在 std::string 的历史中,出现过几种不同的方式。下面我们来一一揭晓。 我们可以从一个简单的问题来探索,一个 std::string 对象占据的内存空间有多大,即 sizeof(std::string) 的值为多大?如果我们在不同的编译器(VC++, GNU, Clang++)上去测试,可能会发现其值并不相同;即使是GNU,不同的版本,获取的值也是不同的。

虽然历史上的实现有多种,但基本上有三种方式:

  • Eager Copy(深拷贝)
  • COW(Copy-On-Write 写时复制)
  • SSO(Short String Optimization-短字符串优化)

每种实现,std::string都包含了下面的信息:

  • 字符串的大小
  • 能够容纳的字符数量
  • 字符串内容本身

1、Eager Copy

最简单的就是深拷贝了。无论什么情况,都是采用拷贝字符串内容的方式解决。这种实现方式,在需要对字符串进行频繁复制而又并不改变字符串内容时,效率比较低下。所以需要对其实现进行优化,之后便出现了下面的COW的实现方式。

2、COW(Copy-On-Write)

Copy-On-Write的原理是什么?

有一定经验的程序员一定知道,Copy-On-Write一定使用了“引用计数”,那么必然有一个变量类似于RefCnt。当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构时,这个计数会减一,直到最后一个类析构时,此时的RefCnt为1或是0,此时,程序才会真正的Free这块从堆上分配的内存。
是的,引用计数就是string类中写时才拷贝的原理
不过,这个RefCnt该存在在哪里呢?如果存放在string类中,那么每个string的实例都有各自的一套,根本不能共有一个RefCnt,如果是声明成全局变量,或是静态成员,那就是所有的string类共享一个了,这也不行。那么我们应该如何解决呢?

string类在什么情况下触发写时复制(Copy-On-Write)?

当共享同一块内存的类发生内容改变时,才会发生Copy-On-Write。比如string类的[]、=、+=、+、操作符赋值,还有一些string类中诸如insert、replace、append等成员函数,包括类的析构时。
修改数据才会触发Copy-On-Write,不修改当然就不会改啦。这就是托延战术的真谛,非到要做的时候才去做。

Copy-On-Write的实现

当两个 std::string 发生复制构造或者赋值时,不会复制字符串内容,而是增加一个引用计数,然后字符串指针进行浅拷贝,其执行效率为O(1)。只有当需要修改其中一个字符串内容时,才执行真正的复制。 其实现的示意图,有下面两种形式:
image.png
std::string 的数据成员就是:

class string {
private:
    Allocator _allocator;
    size_t size;
    size_t capacity;
    char * pointer;
};

第二种形式为:
image.png
std::string 的数据成员就只有一个了。

class string {
private:
    char * _pointer;
};

为了实现的简单,在GNU4.8.4的中,采用的是实现2的形式。从上面的实现,我们看到引用计数并没有与 std::string 的数据成员放在一起,这是为什么呢?
如果采用第二种方式,可以少一次开辟堆空间的操作,而申请堆空间的行为一定会涉及到系统调用。采用第二种方式可以提高效率。

当执行复制构造或赋值时,引用计数加1, std::string 对象共享字符串内容;当 std::string 对象销毁时, 并不直接释放字符串所在的空间,而是先将引用计数减1,直到引用计数为0时,则真正释放字符串内容所在的空间。根据这个思路,大家可以自己动手实现一下。 大家再思考一下,既然涉及到了引用计数,那么在多线程环境下,涉及到修改引用计数的操作,是否是线程安全的呢?为了解决这个问题,GNU4.8.4的实现中,采用了原子操作。

3、SSO(Short String Optimization)

目前,在VC++、GNU5.x.x以上、Clang++上, std::string 实现均采用了SSO的实现。 通常来说,一个程序里用到的字符串大部分都很短小,而在64位机器上,一个char*指针就占用了8个字节,所以SSO就出现了,其核心思想是:发生拷贝时要复制一个指针,对于小字符串来说,为啥不直接复制整个字符串呢,说不定还没有复制一个指针的代价大。其实现示意图如下:
image.png
std::string 的数据成员就是:

class string {
    union Buffer {
        char * _pointer;
        char _local[16];
    };

    Buffer _buffer;
    size_t _size;
    size_t _capacity;
};

当字符串的长度小于等于15个字节时,buffer直接存放整个字符串;当字符串大于15个字节时,buffer
存放的就是一个指针,指向堆空间的区域。这样做的好处是,当字符串较小时,直接拷贝字符串,放在
string内部,不用获取堆空间,开销小。

4、最佳策略

以上三种方式,都不能解决所有可能遇到的字符串的情况,各有所长,又各有缺陷。综合考虑所有情况
之后,facebook开源的folly库中,实现了一个fbstring, 它根据字符串的不同长度使用不同的拷贝策略,最终每个fbstring对象占据的空间大小都是24字节。

  1. 很短的(0~22)字符串用SSO,23字节表示字符串(包括’\0’),1字节表示长度;
  2. 中等长度的(23~255)字符串用eager copy,8字节字符串指针,8字节size,8字节capacity;
  3. 很长的(大于255)字符串用COW, 8字节指针(字符串和引用计数),8字节size,8字节capacity。

5、线程安全性

两个线程同时对同一个字符串进行操作的话, 是不可能线程安全的, 出于性能考虑, C++并没有为 string 实现线程安全, 毕竟不是所有程序都要用到多线程。 但是两个线程同时对独立的两个 string 操作时, 必须是安全的。COW技术实现这一点是通过原子的对引用 计数进行+1或-1操作。
CPU的原子操作虽然比mutex锁好多了, 但是仍然会带来性能损失, 原因如下:

  • 阻止了CPU的乱性执行。
  • 两个CPU对同一个地址进行原子操作, 会导致cache失效, 从而重新从内存中读数据。
  • 系统通常会lock住比目标地址更大的一片区域,影响逻辑上不相关的地址访问。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java 拦截器-用户无操作超时退出利用Redis

1、授权过滤,只要实现AuthConfigAdapter接口 2、利用Redis token超时时间,用户访问后台续时 效果 Component public class AuthFilter implements Filter {private static Logger logger LoggerFactory.getLogger(AuthFilter.class);Autowiredprivat…

【Docker学习】深入研究命令docker exec

使用docker的过程中,我们会有多重情况需要访问容器。比如希望直接进入MySql容器执行命令,或是希望查看容器环境,进行某些操作或访问。这时就会用到这个命令:docker exec。 命令: docker container exec 描述&#x…

SAP FS00如何导出会计总账科目表

输入T-code : S_ALR_87012333 根据‘FS00’中找到的总账科目,进行筛选执行 点击左上角的列表菜单,选择‘电子表格’导出即可

spiderfoot一键扫描IP信息(KALI工具系列九)

目录 1、KALI LINUX简介 2、spiderfoot工具简介 3、在KALI中使用spiderfoot 3.1 目标主机IP(win) 3.2 KALI的IP 4、命令示例 4.1 web访问 4.2 扫描并进行DNS解析 4.3 全面扫描 5、总结 1、KALI LINUX简介 Kali Linux 是一个功能强大、多才多…

vcpkg环境配置

vcpkg 使用linux相关库,设置环境变量VCPKG_ROOT,设置cmake工具链$VCPKG_ROOT/scripts\buildsystems\vcpkg.cmake set VCPKG_DEFAULT_TRIPLETx64-windows .\vcpkg.exe install fftw3 freetype gettext glibmm gtkmm libjpeg-turbo libpng libxmlpp libs…

2010-2022年各省新质生产力数据(含原始数据+测算代码+计算结果)

2010-2022年各省新质生产力数据(含原始数据测算代码计算结果) 1、时间:2010-2022年 2、范围:31省 3、指标:gdp(亿元)、在岗职工工资:元、第三产业就业比重、人均受教育平均年限、…

变分自动编码器(VAE)深入理解与总结

本文导航 0 引言1 起源1.1 自编码器的任务定义1.2 自编码器存在的问题1.3 VAE的核心思路 2 VAE的建模过程2.1 VAE的任务定义2.2 真实分布 ϕ \phi ϕ是什么,为什么要逼近这个分布的参数,如何做?2.3 “重参数化(Reparameterization…

TransFormer学习之VIT算法解析

1.算法简介 本文主要对VIT算法原理进行简单梳理,下图是一个大佬整理的网络整体的流程图,清晰明了,其实再了解自注意力机制和多头自注意力机制后,再看VIT就很简单了 受到NLP领域中Transformer成功应用的启发,ViT算法尝…

设计模式深度解析:分布式与中心化,IT界两大巨头“华山论剑”

​🌈 个人主页:danci_ 🔥 系列专栏:《设计模式》《MYSQL应用》 💪🏻 制定明确可量化的目标,坚持默默的做事。 ✨IT界的两大巨头交锋✨ 👋 在IT界的广阔天地中,有两座…

Qt 界面上控件自适应窗体大小 - 随窗体缩放

Qt 界面上控件自适应窗体大小 - 随窗体缩放 引言一、在Qt Designer上设置二、参数详解三、参考链接 引言 添加布局,设置控件的minimumSize、maximumSize和sizePolicy可以使其跟随窗体进行自适应缩放 - 如上图所示。 一、在Qt Designer上设置 在代码中设置效果一致…

c语言:模拟strlen(三种方法)最全版本

1.计数的方法 #include <stdio.h> #include <assert.h> int my_strlen(const char * str)//const的使用优化 {int count0;assert(str)while(*str){count;str;}return count; } 2.用指针的方法&#xff08;指针-指针&#xff09; #include <stdio.h> #incl…

H.机房【蓝桥杯】/数组链式前向星建图+堆优化版dijkstra

机房 数组链式前向星建图堆优化版dijkstra #include<iostream> #include<queue> #include<cstring> #include<vector> using namespace std; typedef pair<int,int> pii; //无向图开两倍 int e[200005],ne[200005],v[200005],h[200005],du[1000…

神器EasyRecovery2024中文电脑版下载!让数据恢复不再难

在数字化时代&#xff0c;数据就是我们的财富。无论是重要的工作报告&#xff0c;还是那些珍贵的生活瞬间照片&#xff0c;或是我们与朋友间的聊天记录&#xff0c;都储存在我们的电脑或手机中。然而&#xff0c;有时候&#xff0c;意外总是突如其来&#xff0c;电脑突然崩溃&a…

python列表生成式的魅力:轻松创建新列表

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 1. 列表生成式的基本结构 2. 列表生成式的进阶应用 3. 结合其他结构使用列表生成式 1. 列表…

基于ChatGPT+RPA的融资融券业务担保资产风险评价

原载《会计之友》2024年第2期 作者简介 李闻一 男&#xff0c;湖北洪湖人&#xff0c;华中师范大学经济与工商管理学院教授、博士生导师&#xff0c;会计学科带头人&#xff0c;研究方向&#xff1a;财务共享、公司金融、风险管理 黄怡凡 女&#xff0c;湖北公安人&#xf…

福昕PDF使用技巧

因为突然间学校的企业版WPS突然很多功能就不能使用了&#xff0c;所以转向福昕PDF。 一、合并文件 添加需要合并的文件&#xff0c;可以使用ctrla等方式全选 找到最上方的“合并文件” 二、文本注释

基于51单片机的超声波液位测量与控制系统

基于51单片机液位控制器 &#xff08;仿真&#xff0b;程序&#xff0b;原理图PCB&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.使用HC-SR04测量液位&#xff0c;LCD1602显示&#xff1b; 2.当水位高于设定上限的时候&#xff0c;对应声光报警报警&am…

IPv4 报头 Protocol 字段和 IPv6 报头 Next header 字段中的 IP 协议号列表

IPv4 基本报头&#xff08;20 ~ 60 Byte&#xff09; IPv6 基本报头&#xff08;40 Byte&#xff09; IPv4 Header vs IPv6 Header 黄色 为 IPv6 与 IPv4 相同 红色 为 IPv6 删除的 蓝色 为名称不同功能相同 中青色 为新增的 Type of service Traffic Class &#xff08;用于…

攻防世界[GoodRe]

攻防世界[GoodRe] 学到知识&#xff1a; 逆向的精髓&#xff1a;三分懂&#xff0c;七分蒙。TEA 算法快速识别&#xff08;蒙&#xff09;&#xff1a; 数据处理的形式&#xff1a;进入加密时的数据和加密结束后的数据&#xff0c;处理时数据的分组等等&#xff0c;都能用来…

STM32应用开发进阶--IIC总线(SHT20温湿度+HAL库_硬件I2C)

实现目标 1、掌握IIC总线基础知识&#xff1b; 2、会使用软件模拟IIC总线和使用STM32硬件IIC总线&#xff1b; 3、 学会STM32CubeMX软件关于IIC的配置; 4、掌握SHT20温湿度传感器的驱动&#xff1b; 5、具体目标&#xff1a;&#xff08;1&#xff09;用STM32硬件IIC驱动S…