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

news2024/11/16 6:32:08

对于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住比目标地址更大的一片区域,影响逻辑上不相关的地址访问。

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

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

相关文章

JavaSE 学习记录

1. Java 内存 2. this VS super this和super是两个关键字,用于引用当前对象和其父类对象 this 关键字: this 关键字用于引用当前对象,即调用该关键字的方法所属的对象。 主要用途包括: 在类的实例方法中,通过 this …

鸿蒙ArkTS声明式开发:跨平台支持列表【触摸事件】

触摸事件 当手指在组件上按下、滑动、抬起时触发。 说明: 开发前请熟悉鸿蒙开发指导文档: gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 从API Version 7开始支持。后续版本如有新增内容,则采用上角标单独…

Spring从零开始学使用系列(四)之@PostConstruct和@PreDestroy注解的使用

如果各位老爷觉得可以,请点赞收藏评论,谢谢啦!! 文章中涉及到的图片均由AI生成 公众号在最下方!!! 目录 1. 介绍 1.1 PostConstruct概述 1.2 PreDestroy概述 2. 基本用法 2.1 注册CommonAnn…

Docker进入容器查看内容并从容器里拷贝文件到宿主机

工作中需要从docker正在运行的镜像中复制文件到宿主机,于是便将这个过程记录了下来。 (1)查看正在运行的容器 通过以下命令,可以查看正在运行的容器: docker ps (2)进入某个容器执行脚本 我…

Pytest对协程异步函数进行单元测试

安装 安装基础包 pytest,pytest-asyncio pip install pytest pytest-asyncio测试: pytest -s -v ./python-code/do-async/aiohttp_session_pytest.py书写规范 类名必须以 Test 开头方法和函数名必须以test开头 class TestAddFunc(object): # 测试…

【mysql】in和exists的区别,not in、not exists、left join的相互转换

【mysql】in和exists的区别,not in、not exists、left join的相互转换 【一】in介绍【1】in中数据量的限制【2】null值不参与in或not in,也就是说in and not in 并不是全量值,排除了null值【3】in的执行逻辑 【二】exists介绍【1】exists no…

学 Go 具体能干什么?

学习 Go (Golang) 后,你可以从事许多不同的工作和项目,Go 语言以其高性能、并发处理和简洁的语法而闻名,特别适合以下几个领域: 1. 后端开发 Go 在后端开发中非常流行,特别适合构建高性能的 Web 服务和 API。 Web 框…

前端开发-添加公用的ts文件,并在Vue文件中引用

一般我们把页面要用的公用函数写在一个ts文件中 通过调用这个ts文件让我们可以在vue文件中使用函数 Eg:我们现在创建一个formRules.ts文件 然后在我们需要调用该函数体的vue文件中 import { required } from "/utils/formRules";有可能语法一开始会提示…

Shell环境变量深入:自定义系统环境变量

Shell环境变量深入:自定义系统环境变量 目标 能够自定义系统级环境变量 全局配置文件/etc/profile应用场景 当前用户进入Shell环境初始化的时候会加载全局配置文件/etc/profile里面的环境变量, 供给所有Shell程序使用 以后只要是所有Shell程序或命令使用的变量…

推荐丨快速申请免费域名证书

背景: 域名是一个IP地址上的“面具” 。一个域名的目的是便于记忆和沟通的一组服务器的地址(网站,电子邮件,FTP等)。 通俗的说,域名就相当于一个家庭的门牌号码,别人通过这个号码可以很容易的找到你。 域名不仅便于记…

Python轻松玩转excel操作指导

目录 一、一图概览 二、表格操作 三、内容操作 四、单元格操作 五、Pandas实现表格操作 六、常见场景示例 一、一图概览 ​ ​本文主要对openpyxl库的常用表格操作进行了梳理,熟练的运用后可极大地提升工作效率。 二、表格操作 #创建一个表格sheet.xlsx #…

python数据分析-基于数据挖掘对APP评分的预测

前言 当我们谈论关于APP用户分析与电子商务之间的联系时,机器学习在这两个领域的应用变得至关重要。App用户分析和电子商务之间存在着密切的关联,因为用户行为和偏好的深入理解对于提高用户体验、增加销售以及优化产品功能至关重要。故本文基于K-近邻模…

揭秘C++ String容器:字符串操作的艺术

目录 ​编辑 引言 一、初识std::string:构造与初始化 二、字符串的操纵艺术:拼接、查找与替换 三、访问与遍历:字符的细腻触感 四、大小与容量:动态调整的智慧 五、进阶功能:探索更多可能 结语 引言 在C标准库…

弱类型解析

php中 转化为相同类型后比较 先判断数据类型后比较数值 var_dump("asdf"0);#bool(true) var_dump("asdf"1);#bool(false) var_dump("0asdf"0);#bool(true) var_dump("1asdf"1);#bool(true)1、md5撞库 例&#xff1a; <?php incl…

【Python从入门到进阶】55、使用Python轻松操作Mysql数据库

一、引言 1、MySQL数据库简介 MySQL是一个开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它使用了一种名为Structured Query Language&#xff08;SQL&#xff09;的查询语言来管理数据。MySQL因其高性能、可扩展性、易用性和稳定性而广受欢迎&#x…

【论文复现】LSTM长短记忆网络

LSTM 前言网络架构总线遗忘门记忆门记忆细胞输出门 模型定义单个LSTM神经元的定义LSTM层内结构的定义 模型训练模型评估代码细节LSTM层单元的首尾的处理配置Tensorflow的GPU版本 前言 LSTM作为经典模型&#xff0c;可以用来做语言模型&#xff0c;实现类似于语言模型的功能&am…

数据结构:树(3)【二叉树链式结构实现】【二叉树的前序,中序,后序遍历】【求二叉树全部结点个数】【求二叉树叶子结点个数】【求二叉树的深度】【单值二叉树】

一.二叉树链式结构的实现 二叉树的链式结构的实现相对于顺序结构的实现就没有那么多的讲究了。就是普通的链表&#xff0c;只不过多了一个指向的指针。 具体结构如下&#xff1a; typedef int BTDataType; typedef struct BinaryTreeNode {BTDataType data;struct BinaryTre…

云计算和大数据处理

文章目录 1.云计算基础知识1.1 基本概念1.2 云计算分类 2.大数据处理基础知识2.1 基础知识2.3 大数据处理技术 1.云计算基础知识 1.1 基本概念 云计算是一种提供资源的网络&#xff0c;使用者可以随时获取“云”上的资源&#xff0c;按需求量使用&#xff0c;并且可以看成是无…

一.架构设计

架构采用 ddd 架构&#xff0c;不同于传统简单的三层的架构&#xff0c;其分层的思想对于大家日后都是很有好处的&#xff0c;会给大家的思想层级&#xff0c;提高很多。 传统的项目 现有的架构 采取ddd架构&#xff0c;给大家在复杂基础上简化保留精髓&#xff0c;一步步进行…

以太坊钱包

以太坊钱包是你通往以太坊系统的门户。它拥有你的密钥&#xff0c;并且可以代表你创建和广播交易。选择一个以太坊钱包可能很困难&#xff0c;因为有很多不同功能和设计选择。有些更适合初学者&#xff0c;有些更适合专家。即使你现在选择一个你喜欢的&#xff0c;你可能会决定…