015 《Folly SmallVector 权威指南》
🌟🌟🌟本文案由Gemini 2.0 Flash Thinking Experimental 01-21创作,用来辅助学习知识。🌟🌟🌟
书籍大纲
▮▮▮▮ 1. chapter 1: 走进 Folly SmallVector(走进 Folly SmallVector)
▮▮▮▮▮▮▮ 1.1 初识 Folly 与 SmallVector(初识 Folly 与 SmallVector):Folly 库概览及其在现代 C++ 开发中的作用
▮▮▮▮▮▮▮ 1.2 SmallVector 的设计哲学与优势(SmallVector 的设计哲学与优势):对比 std::vector,解析 SmallVector 在特定场景下的性能优势和适用性
▮▮▮▮▮▮▮ 1.3 SmallVector 的应用场景分析(SmallVector 的应用场景分析):从嵌入式系统到高性能服务器,SmallVector 的用武之地
▮▮▮▮▮▮▮ 1.4 环境搭建与快速上手(环境搭建与快速上手):如何引入 Folly 库,编写你的第一个 SmallVector 程序
▮▮▮▮ 2. chapter 2: SmallVector 基础:核心概念与用法(SmallVector 基础:核心概念与用法)
▮▮▮▮▮▮▮ 2.1 模板参数详解:Capacity 与 Size(模板参数详解:Capacity 与 Size):深入理解 SmallVector 的内存布局,静态容量与动态增长
▮▮▮▮▮▮▮ 2.2 构造、析构与赋值:对象的生命周期管理(构造、析构与赋值:对象的生命周期管理):各种构造函数、拷贝与移动语义、析构行为详解
▮▮▮▮▮▮▮ 2.3 元素访问与迭代器(元素访问与迭代器):operator[]、at()、front()、back(),begin()、end()、rbegin()、rend() 的使用
▮▮▮▮▮▮▮ 2.4 容量管理与元素操作(容量管理与元素操作):size()、capacity()、empty()、reserve()、shrink_to_fit(),push_back()、pop_back()、insert()、erase()、clear() 等常用 API
▮▮▮▮▮▮▮▮▮▮▮ 2.4.1 实战代码:动态数组的实现(实战代码:动态数组的实现):使用 SmallVector 实现一个可动态增长的数组类
▮▮▮▮▮▮▮▮▮▮▮ 2.4.2 最佳实践:选择合适的初始容量(最佳实践:选择合适的初始容量):避免不必要的内存分配和拷贝
▮▮▮▮▮▮▮ 2.5 类型安全与异常处理(类型安全与异常处理):SmallVector 的类型安全性,异常安全编程实践
▮▮▮▮ 3. chapter 3: SmallVector 进阶:深入原理与高级特性(SmallVector 进阶:深入原理与高级特性)
▮▮▮▮▮▮▮ 3.1 内存分配策略:栈上空间与堆上空间(内存分配策略:栈上空间与堆上空间):SmallVector 的内存管理机制深度剖析
▮▮▮▮▮▮▮ 3.2 移动语义与性能优化(移动语义与性能优化):利用移动语义减少拷贝开销,提升性能
▮▮▮▮▮▮▮ 3.3 emplace_back 与 in-place 构造(emplace_back 与 in-place 构造):高效元素插入方法,避免临时对象产生
▮▮▮▮▮▮▮ 3.4 自定义分配器(Custom Allocator)的应用(自定义分配器(Custom Allocator)的应用):高级内存管理技巧,定制化内存分配策略
▮▮▮▮▮▮▮ 3.5 与 std::vector 的性能对比评测(与 std::vector 的性能对比评测):基准测试与性能分析,量化 SmallVector 的优势
▮▮▮▮▮▮▮▮▮▮▮ 3.5.1 benchmark 代码示例(benchmark 代码示例):使用 Google Benchmark 或其他工具进行性能测试
▮▮▮▮ 4. chapter 4: SmallVector 高级应用与实战案例(SmallVector 高级应用与实战案例)
▮▮▮▮▮▮▮ 4.1 在高性能计算中的应用(在高性能计算中的应用):利用 SmallVector 优化计算密集型应用
▮▮▮▮▮▮▮ 4.2 在网络编程中的应用(在网络编程中的应用):高效处理网络数据包,提升网络服务性能
▮▮▮▮▮▮▮ 4.3 在游戏开发中的应用(在游戏开发中的应用):优化游戏引擎的数据结构,提升帧率
▮▮▮▮▮▮▮ 4.4 与其他 Folly 组件的协同工作(与其他 Folly 组件的协同工作):例如 FBString, FBVector 等
▮▮▮▮▮▮▮ 4.5 案例分析:开源项目中的 SmallVector 应用(案例分析:开源项目中的 SmallVector 应用):学习知名开源项目如何使用 SmallVector
▮▮▮▮ 5. chapter 5: SmallVector API 全面解析(SmallVector API 全面解析)
▮▮▮▮▮▮▮ 5.1 构造函数与赋值运算符详解(构造函数与赋值运算符详解):深入剖析各种构造函数和赋值运算符的参数、返回值和异常
▮▮▮▮▮▮▮ 5.2 容量函数详解(容量函数详解):size()、capacity()、max_size()、empty()、reserve()、shrink_to_fit()
▮▮▮▮▮▮▮ 5.3 元素访问函数详解(元素访问函数详解):operator[]、at()、front()、back()、data()
▮▮▮▮▮▮▮ 5.4 修改器函数详解(修改器函数详解):push_back()、emplace_back()、pop_back()、insert()、emplace()、erase()、clear()、assign()、resize()、swap()
▮▮▮▮▮▮▮ 5.5 迭代器函数详解(迭代器函数详解):begin()、end()、cbegin()、cend()、rbegin()、rend()、crbegin()、crend()
▮▮▮▮ 6. chapter 6: SmallVector 常见问题与最佳实践(SmallVector 常见问题与最佳实践)
▮▮▮▮▮▮▮ 6.1 常见错误用法与陷阱(常见错误用法与陷阱):避免 SmallVector 使用中的常见错误
▮▮▮▮▮▮▮ 6.2 性能调优技巧(性能调优技巧):提升 SmallVector 应用性能的实用技巧
▮▮▮▮▮▮▮ 6.3 与其他容器的选择:std::vector, std::array, std::deque 等(与其他容器的选择:std::vector, std::array, std::deque 等):根据不同场景选择最合适的容器
▮▮▮▮▮▮▮ 6.4 SmallVector 的未来发展趋势(SmallVector 的未来发展趋势):展望 SmallVector 的发展方向和潜在改进
▮▮▮▮ 7. chapter 7: 总结与展望(总结与展望)
▮▮▮▮▮▮▮ 7.1 SmallVector 的价值与意义回顾(SmallVector 的价值与意义回顾)
▮▮▮▮▮▮▮ 7.2 持续学习与深入探索 Folly 库(持续学习与深入探索 Folly 库)
1. chapter 1: 走进 Folly SmallVector(走进 Folly SmallVector)
1.1 初识 Folly 与 SmallVector(初识 Folly 与 SmallVector):Folly 库概览及其在现代 C++ 开发中的作用
在现代 C++ 开发的浩瀚星空中,Folly
库犹如一颗璀璨的明星,以其卓越的性能和丰富的功能,照亮了无数工程师的编程之路。Folly
,全称 "Facebook Open-source Library",是由 Facebook 开源的一套高度模块化的 C++ 库。它并非一个大而全的框架,而是一系列专注于解决实际工程问题的工具和组件的集合。Folly
库的设计哲学是拥抱现代 C++ 标准,追求极致的性能和效率,并提供在标准库之外的实用工具,以应对高性能、高并发场景下的挑战。
Folly
库涵盖了广泛的领域,从基础的数据结构与算法,到并发编程、网络编程、序列化与反序列化,再到时间处理、字符串操作等等,几乎现代 C++ 开发的方方面面都能在 Folly
中找到相应的解决方案。它不仅仅是对标准库的简单扩展,更是在许多方面进行了创新和优化,例如:
① 高性能数据结构:Folly
提供了许多高性能的数据结构,例如 FBVector
、FBString
、F14ValueMap
、F14FastMap
等,它们在特定场景下比标准库容器拥有更出色的性能。
② 强大的并发工具:Folly
提供了丰富的并发编程工具,如 Futures
、Promises
、Executors
等,使得异步编程和并发控制更加简洁高效。
③ 高效的网络编程库:Folly
包含 Asio
的扩展和封装,以及 Proxygen
HTTP 框架,为构建高性能网络应用提供了坚实的基础。
④ 实用的工具函数:Folly
提供了大量的实用工具函数,涵盖字符串处理、时间操作、类型转换、位操作等,极大地提高了开发效率。
在 Folly
众多组件中,SmallVector
凭借其独特的内存管理策略和卓越的性能,在各种应用场景中脱颖而出,成为备受关注的焦点。SmallVector
是一种混合型容器(Hybrid Container),它巧妙地结合了静态数组(Static Array)和动态数组(Dynamic Array)的优点。与 std::vector
总是将元素存储在堆(Heap)上不同,SmallVector
尝试将元素存储在栈(Stack)上的一小块预留空间中,只有当元素数量超过预留空间时,才会退化为在堆上分配内存的动态数组。这种设计使得 SmallVector
在元素数量较小时,能够避免堆内存分配和释放的开销,从而获得更高的性能。
SmallVector
的出现并非要完全替代 std::vector
,而是在特定场景下提供一种更优的选择。它尤其适用于以下情况:
⚝ 元素数量通常较小:当你知道容器中存储的元素数量通常在一个较小的范围内时,SmallVector
可以充分发挥其栈上存储的优势,避免堆内存操作的开销。
⚝ 对性能有较高要求:在性能敏感的应用中,例如游戏开发、实时系统、高性能计算等,SmallVector
可以通过减少内存分配和拷贝,显著提升程序性能。
⚝ 内存资源受限:在嵌入式系统或资源受限的环境中,SmallVector
的栈上存储特性可以减少堆内存的使用,降低内存碎片产生的风险。
总而言之,Folly
库是现代 C++ 开发中不可或缺的利器,而 SmallVector
则是 Folly
库中一颗耀眼的明珠。掌握 SmallVector
的原理和用法,能够帮助我们编写出更加高效、可靠的 C++ 程序,并在各种应用场景中游刃有余。在接下来的章节中,我们将深入探索 SmallVector
的奥秘,从基础概念到高级应用,带你领略 SmallVector
的魅力。
1.2 SmallVector 的设计哲学与优势(SmallVector 的设计哲学与优势):对比 std::vector,解析 SmallVector 在特定场景下的性能优势和适用性
为了更好地理解 SmallVector
的设计哲学和优势,我们首先需要将其与 C++ 标准库中最为常用的动态数组容器 std::vector
进行对比。std::vector
以其灵活性和易用性,成为了 C++ 开发中最受欢迎的容器之一。然而,std::vector
的实现方式在某些场景下也存在一定的局限性,而 SmallVector
正是为了弥补这些局限性而诞生的。
std::vector
的核心设计思想是将元素存储在堆(Heap)上动态分配的内存中。无论 std::vector
存储多少个元素,甚至即使是空的,它都会在堆上分配一块内存来管理这些元素。这种设计保证了 std::vector
可以动态地增长和收缩,适应各种大小的数据集。但是,堆内存的分配和释放相对于栈(Stack)内存操作来说,开销更大。每次在堆上分配内存,都需要操作系统进行内存管理,这涉及到查找空闲内存块、更新内存分配表等操作,而栈内存的分配和释放仅仅是移动栈顶指针,效率非常高。
SmallVector
的设计哲学正是为了利用栈内存的快速分配和释放特性,来优化小规模数据集的处理性能。SmallVector
的核心思想是:在栈上预留一小块固定大小的内存空间,用于存储容器的前几个元素。只有当元素数量超过栈上空间时,才会在堆上动态分配内存,并将栈上的元素拷贝到堆上。 这种混合内存管理策略使得 SmallVector
在处理小规模数据集时,可以完全避免堆内存分配和释放的开销,从而获得接近于静态数组的性能,同时又保留了动态数组的灵活性。
为了更清晰地对比 std::vector
和 SmallVector
的差异,我们可以从以下几个方面进行分析:
① 内存分配位置:
▮▮▮▮⚝ std::vector
:始终在堆上分配内存。
▮▮▮▮⚝ SmallVector
:优先在栈上分配内存,超出容量时退化到堆上。
② 内存分配开销:
▮▮▮▮⚝ std::vector
:堆内存分配和释放开销相对较大。
▮▮▮▮⚝ SmallVector
:小规模数据时,栈内存分配和释放开销极小;大规模数据时,堆内存开销与 std::vector
相当。
③ 性能特点:
▮▮▮▮⚝ std::vector
:通用性强,适用于各种规模的数据集,但小规模数据性能并非最优。
▮▮▮▮⚝ SmallVector
:小规模数据性能极佳,接近静态数组;大规模数据性能与 std::vector
接近。
④ 适用场景:
▮▮▮▮⚝ std::vector
:通用动态数组,适用于大多数场景。
▮▮▮▮⚝ SmallVector
:元素数量通常较小,对性能有较高要求的场景,例如:
▮▮▮▮▮▮▮▮⚝ 存储函数调用的小型参数列表。
▮▮▮▮▮▮▮▮⚝ 编译器中的小型符号表。
▮▮▮▮▮▮▮▮⚝ 图形渲染中的小型顶点列表。
▮▮▮▮▮▮▮▮⚝ 网络编程中的小型数据包头部。
⑤ 空间占用:
▮▮▮▮⚝ std::vector
:始终需要在堆上分配内存,即使容器为空,也可能占用一定的堆内存。
▮▮▮▮⚝ SmallVector
:栈上空间是预先分配的,无论是否使用都会占用栈空间;堆上空间只有在需要时才会分配。
总结来说,SmallVector
的优势在于:
▮▮▮▮ⓐ 性能优势:对于小规模数据集,SmallVector
可以显著提升性能,因为它避免了堆内存分配和释放的开销。栈内存操作的速度远快于堆内存操作,尤其是在频繁创建和销毁容器的场景下,性能提升更为明显。
▮▮▮▮ⓑ 内存局部性:栈内存通常在 CPU 缓存中命中率更高,因为栈内存的访问模式往往是连续的,而堆内存的访问模式则可能比较分散。更高的缓存命中率意味着更快的内存访问速度,从而提升程序整体性能。
▮▮▮▮ⓒ 避免堆碎片:频繁的堆内存分配和释放容易导致堆碎片,降低内存利用率,甚至影响程序性能。SmallVector
在小规模数据场景下,可以减少堆内存的使用,从而降低堆碎片产生的风险。
当然,SmallVector
也并非完美无缺。它的栈上空间是固定的,如果预留空间过小,则容易频繁退化到堆上,反而可能降低性能;如果预留空间过大,则会浪费栈内存。因此,选择合适的栈上空间大小是使用 SmallVector
的关键。此外,SmallVector
的适用场景相对有限,主要集中在小规模数据集的处理。对于大规模数据集,std::vector
仍然是更合适的选择。
在实际应用中,我们需要根据具体的场景和需求,权衡 std::vector
和 SmallVector
的优缺点,选择最合适的容器。如果你的应用场景符合 SmallVector
的适用条件,那么它将为你带来显著的性能提升。
1.3 SmallVector 的应用场景分析(SmallVector 的应用场景分析):从嵌入式系统到高性能服务器,SmallVector 的用武之地
SmallVector
以其独特的性能优势,在各种不同的应用领域都找到了用武之地。从资源受限的嵌入式系统,到追求极致性能的高性能服务器,SmallVector
都能发挥其独特的价值。下面我们将深入分析 SmallVector
在不同场景下的应用,帮助你更好地理解它的适用性和优势。
① 嵌入式系统(Embedded Systems)
嵌入式系统通常具有资源受限的特点,例如内存容量有限、CPU 性能较低等。在这些环境下,内存管理的效率至关重要。std::vector
的堆内存分配方式可能会带来额外的开销,并增加内存碎片产生的风险。而 SmallVector
的栈上存储特性,可以有效地减少堆内存的使用,降低内存分配和释放的开销,并减少内存碎片。
在嵌入式系统中,SmallVector
可以应用于以下场景:
⚝ 小型数据缓冲区:例如,在传感器数据采集、通信协议处理等场景中,经常需要使用小型的数据缓冲区来暂存数据。SmallVector
可以作为高效的数据缓冲区容器,避免频繁的堆内存操作。
⚝ 配置参数存储:嵌入式设备的配置参数通常数量较少,且在程序运行期间很少变化。使用 SmallVector
存储配置参数,可以减少堆内存的使用,提高配置参数的访问速度。
⚝ 临时数据结构:在某些算法或任务处理过程中,可能需要创建一些临时的、小规模的数据结构。SmallVector
可以作为这些临时数据结构的容器,快速创建和销毁,而无需担心堆内存开销。
② 高性能计算(High-Performance Computing, HPC)
高性能计算领域对程序性能有着极致的追求。在计算密集型应用中,任何细微的性能提升都可能带来巨大的效益。SmallVector
通过减少内存分配和拷贝,可以显著提升程序性能,尤其是在处理小规模数据集时。
在高性能计算中,SmallVector
可以应用于以下场景:
⚝ 小型向量和矩阵:在数值计算、线性代数等领域,经常需要处理小型向量和矩阵。SmallVector
可以作为这些小型向量和矩阵的底层存储容器,提高计算效率。
⚝ 临时计算结果存储:在复杂的计算过程中,可能会产生大量的临时计算结果。使用 SmallVector
存储这些临时结果,可以减少内存分配和释放的开销,加速计算过程。
⚝ 算法优化:在某些算法中,例如排序、搜索等,可以使用 SmallVector
来优化数据结构,提高算法执行效率。
③ 网络编程(Network Programming)
网络编程对数据处理速度和效率有着很高的要求。在网络服务器中,需要快速处理大量的网络数据包。SmallVector
可以高效地处理网络数据包,提升网络服务性能。
在网络编程中,SmallVector
可以应用于以下场景:
⚝ 网络数据包头部存储:网络数据包的头部通常较小,且结构固定。使用 SmallVector
存储数据包头部,可以快速访问和处理头部信息,提高网络数据包的处理速度。
⚝ 小型消息队列:在某些轻量级的网络服务中,可以使用小型消息队列来缓冲网络数据。SmallVector
可以作为高效的消息队列容器,提高消息的入队和出队速度。
⚝ 临时数据缓存:在网络数据处理过程中,可能需要临时缓存一些数据。SmallVector
可以作为临时数据缓存容器,快速存储和访问数据。
④ 游戏开发(Game Development)
游戏开发对性能和帧率有着极高的要求。游戏引擎需要处理大量的游戏对象、场景数据、用户输入等。SmallVector
可以优化游戏引擎的数据结构,提升游戏帧率,改善游戏体验。
在游戏开发中,SmallVector
可以应用于以下场景:
⚝ 游戏对象组件存储:游戏对象通常由多个组件构成,例如位置组件、渲染组件、物理组件等。使用 SmallVector
存储游戏对象的组件列表,可以快速访问和管理组件。
⚝ 场景图节点存储:游戏场景通常使用场景图来组织和管理游戏对象。SmallVector
可以作为场景图节点的子节点容器,提高场景图的遍历和渲染效率。
⚝ 用户输入事件队列:游戏需要快速响应用户的输入事件。使用 SmallVector
作为用户输入事件队列,可以高效地处理用户输入,保证游戏的流畅性。
⑤ 其他应用场景
除了以上列举的场景,SmallVector
还可以应用于其他许多领域,例如:
⚝ 编译器开发:编译器可以使用 SmallVector
存储小型符号表、临时数据结构等,提高编译速度。
⚝ 数据库系统:数据库系统可以使用 SmallVector
存储小型查询结果集、临时数据缓存等,提高查询效率。
⚝ 音视频处理:音视频处理软件可以使用 SmallVector
存储小型音频帧、视频帧等,提高处理速度。
总而言之,SmallVector
的应用场景非常广泛,只要涉及到小规模数据集的处理,并且对性能有一定要求,SmallVector
都有可能成为一个有力的工具。在实际应用中,我们需要根据具体的场景和需求,评估 SmallVector
的适用性,并进行性能测试,以确定是否能够带来预期的性能提升。
1.4 环境搭建与快速上手(环境搭建与快速上手):如何引入 Folly 库,编写你的第一个 SmallVector 程序
要开始使用 Folly
库中的 SmallVector
,首先需要搭建开发环境并引入 Folly
库。由于 Folly
库依赖于一些第三方库和工具,环境搭建过程可能相对复杂。但是,一旦环境搭建完成,就可以轻松地使用 Folly
提供的各种强大功能。
① 环境准备
在开始之前,你需要确保你的系统满足以下基本条件:
⚝ 操作系统:Folly
库主要在 Linux 和 macOS 系统上进行开发和测试,Windows 系统也部分支持,但可能需要额外的配置。
⚝ C++ 编译器:Folly
库需要支持 C++14 标准的编译器,推荐使用 GCC 5.0 或更高版本,或者 Clang 3.4 或更高版本。
⚝ CMake:Folly
库使用 CMake 进行构建管理,你需要安装 CMake 3.0 或更高版本。
⚝ Python:Folly
库的构建脚本使用 Python 2.7 或更高版本。
⚝ 其他依赖库:Folly
库依赖于一些第三方库,例如 Boost、glog、gflags、Double-conversion、Libevent、OpenSSL、zlib、LZ4、Snappy 等。具体的依赖库版本和安装方式,可以参考 Folly
官方文档。
② 获取 Folly 库
你可以从 GitHub 上克隆 Folly
库的源代码:
1
git clone https://github.com/facebook/folly.git
2
cd folly
③ 构建 Folly 库
在 folly
目录下,创建一个 build 目录,并使用 CMake 进行配置和构建:
1
mkdir build
2
cd build
3
cmake ..
4
make -j$(nproc) # 使用多核编译加速
5
sudo make install # 可选,将 folly 安装到系统目录
在构建过程中,CMake 会自动检测你的系统环境,并下载和安装 Folly
库的依赖项。如果你的系统已经安装了某些依赖库,CMake 可能会直接使用系统已安装的版本。
④ 编写你的第一个 SmallVector 程序
环境搭建完成后,就可以开始编写你的第一个 SmallVector
程序了。创建一个名为 small_vector_example.cpp
的文件,并输入以下代码:
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
// 创建一个 SmallVector,栈上容量为 4,元素类型为 int
6
folly::SmallVector<int, 4> sv;
7
8
// 向 SmallVector 中添加元素
9
sv.push_back(10);
10
sv.push_back(20);
11
sv.push_back(30);
12
sv.push_back(40);
13
sv.push_back(50); // 超过栈上容量,将分配堆内存
14
15
// 遍历 SmallVector 并打印元素
16
std::cout << "SmallVector elements: ";
17
for (int i = 0; i < sv.size(); ++i) {
18
std::cout << sv[i] << " ";
19
}
20
std::cout << std::endl;
21
22
// 打印 SmallVector 的容量和大小
23
std::cout << "Capacity: " << sv.capacity() << std::endl;
24
std::cout << "Size: " << sv.size() << std::endl;
25
26
return 0;
27
}
⑤ 编译和运行程序
使用 g++ 编译器编译 small_vector_example.cpp
文件,并链接 Folly
库:
1
g++ small_vector_example.cpp -o small_vector_example -lfolly
2
./small_vector_example
如果一切顺利,你将看到以下输出:
1
SmallVector elements: 10 20 30 40 50
2
Capacity: 6
3
Size: 5
这个简单的例子演示了如何创建一个 SmallVector
对象,向其中添加元素,遍历元素,并获取容器的容量和大小。
⑥ 代码解析
⚝ #include <folly/container/SmallVector.h>
: 引入 SmallVector
的头文件。
⚝ folly::SmallVector<int, 4> sv;
: 创建一个 SmallVector
对象 sv
。模板参数 int
指定元素类型为 int
,模板参数 4
指定栈上预留容量为 4。
⚝ sv.push_back(element);
: 使用 push_back()
方法向 SmallVector
尾部添加元素。
⚝ sv.size()
: 返回 SmallVector
中元素的数量。
⚝ sv.capacity()
: 返回 SmallVector
的容量,即容器可以容纳的最大元素数量,而无需重新分配内存。
⚝ sv[i]
: 使用下标运算符 []
访问 SmallVector
中的元素。
通过这个简单的例子,你已经成功地搭建了 Folly
开发环境,并编写了你的第一个 SmallVector
程序。在接下来的章节中,我们将深入学习 SmallVector
的各种功能和用法,探索其更高级的应用场景。
END_OF_CHAPTER
2. chapter 2: SmallVector 基础:核心概念与用法(SmallVector Basics: Core Concepts and Usage)
2.1 模板参数详解:Capacity 与 Size(Template Parameter Details: Capacity and Size)
Folly::SmallVector
是一个非常有用的容器,它旨在结合 std::array
和 std::vector
的优点。为了充分理解 SmallVector
的特性和用法,我们首先需要深入了解其模板参数,特别是 Capacity (容量)
和 Size (大小)
这两个核心概念。
SmallVector
的模板声明通常如下所示:
1
template <typename T, size_t N, typename Allocator = std::allocator<T>>
2
class SmallVector;
这里,我们看到 SmallVector
接受三个模板参数:
① typename T
: 这是存储在 SmallVector
中的元素类型。与 std::vector
类似,T
可以是任何可复制或可移动的类型,例如 int
、float
、自定义类等。这是容器存储数据的基本单元。
② size_t N
: 这是 SmallVector
的静态容量 (static capacity),也是 SmallVector
最关键的特性之一。N
指定了容器在栈上 (stack) 预留的空间大小,以元素个数为单位。这意味着 SmallVector
会尝试在栈上直接分配最多容纳 N
个 T
类型元素的空间。如果实际存储的元素数量小于等于 N
,则所有元素都将存储在栈上,避免了堆内存分配的开销,从而提高了性能。
③ typename Allocator = std::allocator<T>
: 这是一个可选的模板参数,用于指定内存分配器。默认情况下,使用 std::allocator<T>
,即标准的堆内存分配器。你可以自定义分配器来满足特定的内存管理需求,例如使用池分配器或在特定内存区域分配内存。这为高级用户提供了更大的灵活性。
现在,让我们重点解析 Capacity (容量)
和 Size (大小)
这两个概念,它们是理解 SmallVector
内存布局和性能特性的关键:
⚝ Capacity (容量):在 SmallVector
的上下文中,Capacity
有两种含义,这取决于是否发生了堆内存分配:
▮▮▮▮⚝ 静态容量 (Static Capacity):由模板参数 N
决定。这是 SmallVector
在栈上预留的最大元素数量。如果 SmallVector
当前存储的元素数量小于等于 N
,那么它的容量就等于 N
,所有元素都存储在栈上。
▮▮▮▮⚝ 动态容量 (Dynamic Capacity):当需要存储超过 N
个元素时,SmallVector
会像 std::vector
一样,在堆 (heap) 上动态分配内存。此时,动态容量指的是堆上分配的内存可以容纳的元素数量。动态容量通常会大于或等于实际存储的元素数量,以便在后续插入元素时减少内存重新分配的次数。
⚝ Size (大小):Size
指的是 SmallVector
当前实际存储的元素数量。Size
总是小于等于 Capacity
。你可以通过 size()
成员函数获取 SmallVector
的大小。
为了更形象地理解 Capacity
和 Size
的关系,我们可以将 SmallVector
比作一个带有预留座位的容器:
⚝ 静态容量 N
就像是容器预先设置好的座位数量,这些座位是固定存在的,无论是否有人坐。
⚝ Size
就像是当前容器中实际坐了多少人。
内存布局:
当 SmallVector
存储的元素数量小于等于静态容量 N
时,其内存布局大致如下:
1
[ 栈上预留空间 (大小为 N * sizeof(T)) ]
2
[ 元素 1 ]
3
[ 元素 2 ]
4
[ ... ]
5
[ 元素 size() ]
6
[ ... ] (剩余预留空间,未被使用)
所有元素都存储在栈上预留的空间中,访问速度非常快。
当 SmallVector
存储的元素数量超过静态容量 N
时,会发生堆内存分配,其内存布局会变为:
1
[ 栈上小对象 (包含指向堆内存的指针、size、capacity 等信息) ]
2
[ ... ] (栈上其他数据)
3
[ 堆上分配的内存块 (大小动态增长) ]
4
[ 元素 1 ]
5
[ 元素 2 ]
6
[ ... ]
7
[ 元素 size() ]
8
[ ... ] (堆上剩余空间)
此时,SmallVector
对象本身(元数据)仍然在栈上,但元素数据存储在堆上动态分配的内存中,这与 std::vector
的内存布局类似。
动态增长:
当向 SmallVector
中添加元素,且当前 Size
达到 Capacity
时,如果 Capacity
仍然是静态容量 N
,则 SmallVector
会执行以下操作以实现动态增长:
① 在堆上分配一块更大的内存空间,通常是当前 Size
的倍数(例如,2倍)。
② 将栈上存储的元素拷贝或移动到堆上新分配的内存中。
③ 更新 SmallVector
的内部指针,使其指向堆上新分配的内存。
④ 后续的元素将存储在堆上。
这个动态增长的过程与 std::vector
的 resize()
或 reserve()
操作类似,涉及到内存分配和元素拷贝/移动的开销。因此,合理选择静态容量 N
非常重要,可以尽量避免或减少堆内存分配和动态增长的发生,从而提升性能。
总结:
理解 SmallVector
的模板参数 N
以及 Capacity
和 Size
的概念是使用好 SmallVector
的基础。静态容量 N
决定了栈上预留空间的大小,直接影响 SmallVector
在小数据场景下的性能优势。而动态增长机制则保证了 SmallVector
也能像 std::vector
一样处理大数据量的场景。在实际应用中,我们需要根据预期的元素数量和性能需求,合理选择 N
的值,以充分发挥 SmallVector
的优势。
2.2 构造、析构与赋值:对象的生命周期管理(Construction, Destruction and Assignment: Object Lifecycle Management)
SmallVector
作为 C++ 中的类,其对象的生命周期管理遵循 C++ 的 RAII (Resource Acquisition Is Initialization) 原则。理解 SmallVector
的构造、析构和赋值行为对于正确使用和避免资源泄漏至关重要。本节将详细解析 SmallVector
的各种构造函数、析构函数以及赋值运算符,并深入探讨拷贝和移动语义在 SmallVector
中的应用。
构造函数 (Constructors):
SmallVector
提供了多种构造函数,以满足不同的初始化需求:
① 默认构造函数 (Default Constructor):
1
SmallVector<int, 4> sv1; // 静态容量为 4 的 SmallVector,初始大小为 0
默认构造函数创建一个空的 SmallVector
对象,其大小 (size) 为 0,静态容量 (static capacity) 由模板参数 N
决定(本例中为 4)。此时,SmallVector
内部可能尚未分配任何内存,或者仅仅是在栈上预留了空间。
② 填充构造函数 (Fill Constructor):
1
SmallVector<int, 4> sv2(3, 10); // 初始化包含 3 个值为 10 的元素的 SmallVector
填充构造函数接受两个参数:元素数量和初始值。它会创建包含指定数量、且所有元素都被初始化为给定值的 SmallVector
。如果元素数量小于等于静态容量 N
,则元素存储在栈上;否则,会发生堆内存分配。
③ 范围构造函数 (Range Constructor):
1
std::vector<int> vec = {1, 2, 3, 4, 5};
2
SmallVector<int, 4> sv3(vec.begin(), vec.end()); // 使用迭代器范围初始化
范围构造函数接受一对迭代器,表示一个元素范围。它会将该范围内的元素拷贝到新的 SmallVector
中。这使得可以使用其他容器(如 std::vector
、std::array
等)或数组来初始化 SmallVector
。
④ 拷贝构造函数 (Copy Constructor):
1
SmallVector<int, 4> sv4 = sv3; // 拷贝构造,sv4 是 sv3 的副本
2
SmallVector<int, 4> sv5(sv3); // 显式拷贝构造
拷贝构造函数创建一个新的 SmallVector
对象,它是现有 SmallVector
对象(本例中为 sv3
)的深拷贝 (deep copy)。这意味着,如果 sv3
的元素存储在栈上,sv4
和 sv5
也会在栈上分配空间并拷贝元素;如果 sv3
的元素存储在堆上,sv4
和 sv5
会在堆上分配新的内存,并将 sv3
堆上的元素拷贝到新的内存中。拷贝构造保证了新对象与原对象在内容上完全独立。
⑤ 移动构造函数 (Move Constructor):
1
SmallVector<int, 4> sv6 = std::move(sv3); // 移动构造,sv6 “接管” sv3 的资源
2
SmallVector<int, 4> sv7(std::move(sv3)); // 显式移动构造
移动构造函数创建一个新的 SmallVector
对象,它通过移动 (move) 而不是拷贝现有 SmallVector
对象(本例中为 sv3
)的资源来初始化。移动构造通常用于提高性能,特别是当源对象是右值 (rvalue) 或即将被销毁时。对于 SmallVector
而言,移动构造主要涉及转移堆内存的所有权(如果存在堆内存分配),以及拷贝栈上的元数据(如 size、capacity 等)。移动构造后,源对象 sv3
将处于有效但未指定 (valid but unspecified) 的状态,通常其大小会变为 0。
⑥ 初始化列表构造函数 (Initializer List Constructor):
1
SmallVector<int, 4> sv8 = {1, 2, 3}; // 使用初始化列表初始化
2
SmallVector<int, 4> sv9{1, 2, 3}; // 统一初始化
初始化列表构造函数允许使用花括号 {}
包围的初始化列表来创建 SmallVector
对象。这是一种简洁直观的初始化方式,尤其适用于初始化少量元素。
析构函数 (Destructor):
SmallVector
的析构函数负责释放对象所占用的资源。其行为取决于 SmallVector
是否在堆上分配了内存:
⚝ 如果元素存储在栈上:析构函数主要负责销毁栈上存储的元素对象(如果元素是类对象且有析构函数)。由于栈内存由系统自动管理,因此不需要显式释放栈内存。
⚝ 如果元素存储在堆上:析构函数除了销毁元素对象外,还需要释放之前在堆上动态分配的内存。这通常通过调用分配器的 deallocate
函数来完成,以避免内存泄漏。
无论哪种情况,SmallVector
的析构函数都会确保所有元素被正确销毁,并且所有动态分配的内存都被释放,遵循 RAII 原则。
赋值运算符 (Assignment Operators):
SmallVector
提供了拷贝赋值运算符和移动赋值运算符,用于将一个 SmallVector
对象的值赋给另一个已存在的 SmallVector
对象。
① 拷贝赋值运算符 (Copy Assignment Operator):
1
SmallVector<int, 4> sv10;
2
sv10 = sv8; // 拷贝赋值,sv10 变为 sv8 的副本
拷贝赋值运算符将右侧 SmallVector
对象(本例中为 sv8
)的内容拷贝到左侧 SmallVector
对象(本例中为 sv10
)。在赋值之前,sv10
原有的元素会被销毁,并释放其占用的内存(如果需要)。然后,根据 sv8
的内存布局,sv10
会在栈上或堆上分配新的内存,并将 sv8
的元素拷贝到新的内存中。与拷贝构造函数类似,拷贝赋值运算符也执行深拷贝。
② 移动赋值运算符 (Move Assignment Operator):
1
SmallVector<int, 4> sv11;
2
sv11 = std::move(sv8); // 移动赋值,sv11 “接管” sv8 的资源
移动赋值运算符将右侧 SmallVector
对象(本例中为 sv8
)的资源移动到左侧 SmallVector
对象(本例中为 sv11
)。与移动构造函数类似,移动赋值运算符主要涉及转移堆内存的所有权(如果存在堆内存分配),以及拷贝栈上的元数据。在赋值之前,sv11
原有的元素会被销毁,并释放其占用的内存(如果需要)。移动赋值通常比拷贝赋值更高效,因为它避免了元素的拷贝操作。移动赋值后,源对象 sv8
将处于有效但未指定的状态。
总结:
SmallVector
提供了丰富的构造函数和赋值运算符,支持默认构造、填充初始化、范围初始化、拷贝和移动语义以及初始化列表。理解这些构造和赋值操作的行为,特别是拷贝和移动语义的区别,对于编写高效且资源管理正确的 C++ 代码至关重要。析构函数则负责在 SmallVector
对象生命周期结束时,释放其占用的资源,确保内存安全。在实际编程中,应根据具体需求选择合适的构造和赋值方式,以优化性能并避免不必要的资源开销。
2.3 元素访问与迭代器(Element Access and Iterators)
访问 SmallVector
中的元素以及遍历容器是使用 SmallVector
的基本操作。SmallVector
提供了多种方式来访问和操作容器内的元素,包括直接访问、边界检查访问以及迭代器访问。本节将详细介绍 SmallVector
提供的元素访问方法和迭代器,并演示它们的使用方式。
元素访问 (Element Access):
SmallVector
提供了以下成员函数来直接访问容器中的元素:
① operator[]
:下标运算符,提供无边界检查 (no bounds checking) 的元素访问。
1
SmallVector<int, 4> sv = {10, 20, 30};
2
int firstElement = sv[0]; // 访问第一个元素,firstElement = 10
3
sv[1] = 25; // 修改第二个元素的值,sv 变为 {10, 25, 30}
operator[]
允许使用下标索引来访问 SmallVector
中的元素,类似于访问数组。重要提示:operator[]
不进行边界检查。如果访问的索引超出有效范围(即小于 0 或大于等于 size()
),则行为是未定义的 (undefined behavior),可能导致程序崩溃或产生不可预测的结果。因此,在使用 operator[]
时,务必确保索引的有效性。
② at(size_type pos)
:提供有边界检查 (bounds checking) 的元素访问。
1
SmallVector<int, 4> sv = {10, 20, 30};
2
int secondElement = sv.at(1); // 访问第二个元素,secondElement = 20
3
4
try {
5
int element = sv.at(3); // 尝试访问索引为 3 的元素(超出范围)
6
} catch (const std::out_of_range& e) {
7
std::cerr << "Out of range access: " << e.what() << std::endl; // 捕获异常
8
}
at()
成员函数也用于访问指定索引位置的元素,但与 operator[]
不同的是,at()
会进行边界检查。如果访问的索引 pos
超出有效范围(即 pos >= size()
),at()
会抛出一个 std::out_of_range
异常。这使得 at()
更加安全,尤其是在不确定索引是否有效的情况下。使用 try-catch
块可以捕获并处理越界访问异常。
③ front()
:访问第一个元素。
1
SmallVector<int, 4> sv = {10, 20, 30};
2
int first = sv.front(); // 访问第一个元素,first = 10
front()
成员函数返回 SmallVector
中第一个元素的引用 (reference)。前提条件:SmallVector
必须非空 (not empty),即 size() > 0
。如果对空 SmallVector
调用 front()
,行为是未定义的。
④ back()
:访问最后一个元素。
1
SmallVector<int, 4> sv = {10, 20, 30};
2
int last = sv.back(); // 访问最后一个元素,last = 30
back()
成员函数返回 SmallVector
中最后一个元素的引用。前提条件:SmallVector
必须非空。如果对空 SmallVector
调用 back()
,行为是未定义的。
迭代器 (Iterators):
迭代器是 C++ 中用于遍历容器元素的通用机制。SmallVector
提供了多种迭代器类型,以支持不同方向和访问方式的遍历。
① begin()
和 end()
:返回指向容器首元素和尾元素之后位置的迭代器 (iterator)。
⚝ begin()
返回指向 SmallVector
第一个元素的迭代器。
⚝ end()
返回指向 SmallVector
尾元素之后位置的迭代器,即past-the-end iterator。它不指向任何实际元素,而是作为遍历结束的标志。
1
SmallVector<int, 4> sv = {10, 20, 30};
2
for (auto it = sv.begin(); it != sv.end(); ++it) {
3
std::cout << *it << " "; // 输出:10 20 30
4
}
5
std::cout << std::endl;
上述代码使用 begin()
和 end()
返回的迭代器遍历 SmallVector
中的所有元素。auto it = sv.begin()
初始化迭代器 it
指向第一个元素,it != sv.end()
判断是否到达容器末尾,++it
将迭代器移动到下一个元素,*it
解引用迭代器以访问当前元素的值。
② cbegin()
和 cend()
:返回指向容器首元素和尾元素之后位置的常量迭代器 (const_iterator)。
cbegin()
和 cend()
与 begin()
和 end()
类似,但它们返回的是常量迭代器。常量迭代器只能用于读取元素,而不能修改元素的值。这在需要只读访问容器元素时非常有用。
1
SmallVector<int, 4> sv = {10, 20, 30};
2
for (auto it = sv.cbegin(); it != sv.cend(); ++it) {
3
std::cout << *it << " "; // 输出:10 20 30 (只读访问)
4
// *it = 40; // 错误:常量迭代器不能修改元素
5
}
6
std::cout << std::endl;
③ rbegin()
和 rend()
:返回指向容器尾元素和首元素之前位置的反向迭代器 (reverse_iterator)。
⚝ rbegin()
返回指向 SmallVector
最后一个元素的反向迭代器。
⚝ rend()
返回指向 SmallVector
首元素之前位置的反向迭代器,作为反向遍历结束的标志。
1
SmallVector<int, 4> sv = {10, 20, 30};
2
for (auto rit = sv.rbegin(); rit != sv.rend(); ++rit) {
3
std::cout << *rit << " "; // 输出:30 20 10 (反向遍历)
4
}
5
std::cout << std::endl;
上述代码使用 rbegin()
和 rend()
返回的反向迭代器反向遍历 SmallVector
中的元素。
④ crbegin()
和 crend()
:返回指向容器尾元素和首元素之前位置的常量反向迭代器 (const_reverse_iterator)。
crbegin()
和 crend()
与 rbegin()
和 rend()
类似,但它们返回的是常量反向迭代器,用于只读反向遍历。
迭代器失效 (Iterator Invalidation):
在使用迭代器时,需要注意迭代器失效的问题。某些容器操作可能会导致迭代器失效,即迭代器不再指向有效的元素位置。对于 SmallVector
而言,以下操作可能会导致迭代器失效:
⚝ 插入元素 (insert, emplace_back, push_back 等):如果在静态容量范围内插入元素,栈上元素的迭代器通常不会失效。但如果插入元素导致堆内存分配或重新分配,则所有迭代器都可能失效。
⚝ 删除元素 (erase, pop_back, clear 等):删除元素会导致被删除元素之后的所有元素的迭代器失效。
⚝ 调整大小 (resize, reserve, shrink_to_fit 等):调整容器大小可能会导致内存重新分配,从而使所有迭代器失效。
因此,在进行容器操作后,如果之前获取的迭代器可能受到影响,应重新获取迭代器,以确保迭代器的有效性。
总结:
SmallVector
提供了多种灵活的元素访问方式,包括 operator[]
、at()
、front()
、back()
以及各种迭代器。operator[]
提供快速的无边界检查访问,at()
提供安全的有边界检查访问,front()
和 back()
访问首尾元素,而迭代器则支持各种遍历方式。在选择元素访问方式时,需要根据具体场景权衡性能和安全性。同时,要时刻注意迭代器失效的问题,避免因使用失效迭代器而导致程序错误。熟练掌握这些元素访问和迭代器方法,可以有效地操作和遍历 SmallVector
中的数据。
2.4 容量管理与元素操作(Capacity Management and Element Operations)
SmallVector
提供了丰富的成员函数来管理容器的容量和操作容器中的元素。这些函数使得我们可以动态地调整 SmallVector
的大小、预留空间、添加、删除和修改元素。本节将详细介绍 SmallVector
的容量管理和元素操作相关的常用 API。
容量管理 (Capacity Management):
以下成员函数用于查询和管理 SmallVector
的容量:
① size()
:返回 SmallVector
中当前元素的数量,即 Size (大小)。
1
SmallVector<int, 4> sv = {10, 20, 30};
2
size_t currentSize = sv.size(); // currentSize = 3
size()
函数返回一个 size_t
类型的值,表示 SmallVector
当前存储的元素个数。
② capacity()
:返回 SmallVector
的容量,即在不重新分配内存的情况下可以容纳的最大元素数量,即 Capacity (容量)。
1
SmallVector<int, 4> sv = {10, 20, 30};
2
size_t currentCapacity = sv.capacity(); // currentCapacity >= 3, 可能是 4 或更大
capacity()
函数返回一个 size_t
类型的值,表示 SmallVector
当前分配的内存空间可以容纳的元素个数。对于静态容量 N
的 SmallVector
,在未发生堆分配前,capacity()
通常等于 N
。当发生堆分配后,capacity()
可能大于 N
,并且会随着动态增长而变化。
③ empty()
:检查 SmallVector
是否为空,即 size()
是否为 0。
1
SmallVector<int, 4> sv1;
2
bool isEmpty1 = sv1.empty(); // isEmpty1 = true
3
4
SmallVector<int, 4> sv2 = {10, 20};
5
bool isEmpty2 = sv2.empty(); // isEmpty2 = false
empty()
函数返回一个 bool
值,如果 SmallVector
为空(不包含任何元素),则返回 true
,否则返回 false
。
④ reserve(size_type n)
:预留至少能容纳 n
个元素的空间。
1
SmallVector<int, 4> sv;
2
sv.reserve(10); // 预留至少能容纳 10 个元素的空间
3
size_t reservedCapacity = sv.capacity(); // reservedCapacity >= 10
reserve(n)
函数用于预先分配至少能容纳 n
个元素的内存空间。如果 n
大于当前 capacity()
,reserve()
会分配新的内存空间,并将现有元素移动到新的内存中。如果 n
小于或等于当前 capacity()
,reserve()
可能不做任何操作。reserve()
不会改变 size()
,它只影响 capacity()
,用于提前分配内存,以避免后续插入元素时频繁的内存重新分配,从而提高性能。
⑤ shrink_to_fit()
:尝试释放多余的容量,将容量调整为与大小匹配。
1
SmallVector<int, 4> sv;
2
sv.reserve(100); // 预留 100 个元素的空间
3
sv.push_back(1);
4
sv.push_back(2);
5
sv.shrink_to_fit(); // 尝试释放多余容量
6
size_t currentCapacity = sv.capacity(); // currentCapacity 可能接近 2
shrink_to_fit()
函数尝试释放 SmallVector
中多余的容量,将 capacity()
减小到与 size()
匹配或接近的水平。但需要注意的是,shrink_to_fit()
只是一个请求 (request),具体的实现可能并不保证一定能释放内存,或者释放多少内存取决于具体的分配器实现。shrink_to_fit()
主要用于在确定不再需要大量额外空间时,减少内存占用。
元素操作 (Element Operations):
以下成员函数用于添加、删除和修改 SmallVector
中的元素:
① push_back(const T& value)
和 push_back(T&& value)
:在 SmallVector
的末尾添加一个元素。
1
SmallVector<int, 4> sv;
2
sv.push_back(10); // 添加元素 10
3
sv.push_back(20); // 添加元素 20
push_back()
有两个重载版本,分别接受左值引用和右值引用。它在 SmallVector
的末尾添加一个新元素。如果当前 size()
达到 capacity()
,且容量是静态容量 N
,则可能触发堆内存分配和动态增长。
② emplace_back(Args&&... args)
:在 SmallVector
的末尾就地构造 (in-place construct) 一个元素。
1
SmallVector<std::pair<int, std::string>, 4> sv_pairs;
2
sv_pairs.emplace_back(1, "apple"); // 就地构造一个 pair<int, string> 对象
emplace_back()
与 push_back()
类似,也在末尾添加元素,但 emplace_back()
接受可变参数,用于直接在容器内部构造元素对象,而不是先创建临时对象再拷贝或移动。这可以避免不必要的拷贝或移动操作,尤其对于构造复杂对象时,emplace_back()
通常更高效。
③ pop_back()
:移除 SmallVector
的最后一个元素。
1
SmallVector<int, 4> sv = {10, 20, 30};
2
sv.pop_back(); // 移除最后一个元素,sv 变为 {10, 20}
pop_back()
函数移除 SmallVector
的最后一个元素,size()
减 1。前提条件:SmallVector
必须非空。
④ insert(iterator pos, const T& value)
和 insert(iterator pos, T&& value)
:在指定位置 pos
插入一个元素。
1
SmallVector<int, 4> sv = {10, 30};
2
auto it = sv.begin() + 1; // 指向第二个元素的位置
3
sv.insert(it, 20); // 在第二个元素位置插入 20,sv 变为 {10, 20, 30}
insert()
函数在迭代器 pos
指向的位置之前插入一个新元素。插入位置之后的所有元素需要向后移动。insert()
也有左值引用和右值引用两个重载版本。插入元素可能导致内存重新分配和迭代器失效。
⑤ erase(iterator pos)
和 erase(iterator first, iterator last)
:移除指定位置或范围的元素。
1
SmallVector<int, 4> sv = {10, 20, 30, 40};
2
auto it1 = sv.begin() + 1;
3
sv.erase(it1); // 移除第二个元素 (20),sv 变为 {10, 30, 40}
4
5
auto it2 = sv.begin() + 1;
6
auto it3 = sv.begin() + 3;
7
sv.erase(it2, it3); // 移除范围 [it2, it3) 的元素 (30),sv 变为 {10, 40}
erase()
函数可以移除迭代器 pos
指向的单个元素,或者移除迭代器范围 [first, last)
内的元素。移除元素后,后续元素需要向前移动。erase()
操作可能导致迭代器失效。
⑥ clear()
:移除 SmallVector
中的所有元素,使其变为空容器。
1
SmallVector<int, 4> sv = {10, 20, 30};
2
sv.clear(); // 移除所有元素,sv 变为空容器,size() = 0
clear()
函数移除 SmallVector
中的所有元素,但不会释放已分配的内存空间,capacity()
保持不变。
总结:
SmallVector
提供了丰富的容量管理和元素操作 API,包括查询大小和容量、预留空间、释放多余容量、添加元素(push_back
, emplace_back
)、删除元素(pop_back
, erase
, clear
)以及插入元素(insert
)。熟练掌握这些 API,可以灵活地控制 SmallVector
的内存使用和元素内容。在实际应用中,合理使用 reserve()
预留空间可以减少动态增长的开销,shrink_to_fit()
可以减少内存占用,emplace_back()
可以提高元素构造效率,而 erase()
和 clear()
则用于元素的删除和容器的清空。
2.4.1 实战代码:动态数组的实现(Practical Code: Implementation of Dynamic Array)
为了更好地理解 SmallVector
的应用,并演示如何使用其容量管理和元素操作 API,我们来实现一个简单的动态数组 (Dynamic Array) 类,名为 DynamicArray
,其内部使用 Folly::SmallVector
作为底层存储。DynamicArray
类将提供类似于 std::vector
的基本功能,例如添加元素、访问元素、获取大小等。
1
#include <folly/container/SmallVector.h>
2
#include <stdexcept> // for std::out_of_range
3
#include <iostream> // for std::cout
4
5
template <typename T, size_t N>
6
class DynamicArray {
7
private:
8
folly::SmallVector<T, N> data_; // 使用 SmallVector 作为底层存储
9
10
public:
11
DynamicArray() = default; // 默认构造函数
12
13
// 添加元素到末尾
14
void push_back(const T& value) {
15
data_.push_back(value);
16
}
17
18
void push_back(T&& value) {
19
data_.push_back(std::move(value));
20
}
21
22
// 获取元素数量
23
size_t size() const {
24
return data_.size();
25
}
26
27
// 检查是否为空
28
bool empty() const {
29
return data_.empty();
30
}
31
32
// 访问元素 (无边界检查)
33
T& operator[](size_t index) {
34
return data_[index];
35
}
36
37
const T& operator[](size_t index) const {
38
return data_[index];
39
}
40
41
// 访问元素 (有边界检查)
42
T& at(size_t index) {
43
return data_.at(index);
44
}
45
46
const T& at(size_t index) const {
47
return data_.at(index);
48
}
49
50
// 清空数组
51
void clear() {
52
data_.clear();
53
}
54
};
55
56
int main() {
57
// 创建一个静态容量为 4 的 DynamicArray<int, 4> 对象
58
DynamicArray<int, 4> arr;
59
60
// 添加元素
61
arr.push_back(10);
62
arr.push_back(20);
63
arr.push_back(30);
64
arr.push_back(40);
65
arr.push_back(50); // 超过静态容量,会发生堆分配
66
67
// 访问元素
68
std::cout << "Size: " << arr.size() << std::endl; // 输出 Size: 5
69
std::cout << "Element at index 2: " << arr[2] << std::endl; // 输出 Element at index 2: 30
70
std::cout << "Element at index 4: " << arr.at(4) << std::endl; // 输出 Element at index 4: 50
71
72
// 遍历元素
73
std::cout << "Elements: ";
74
for (size_t i = 0; i < arr.size(); ++i) {
75
std::cout << arr[i] << " "; // 输出 Elements: 10 20 30 40 50
76
}
77
std::cout << std::endl;
78
79
// 清空数组
80
arr.clear();
81
std::cout << "Size after clear: " << arr.size() << std::endl; // 输出 Size after clear: 0
82
std::cout << "Is empty: " << (arr.empty() ? "Yes" : "No") << std::endl; // 输出 Is empty: Yes
83
84
return 0;
85
}
代码解析:
① 模板参数:DynamicArray
类也使用了模板,接受两个参数:typename T
表示元素类型,size_t N
表示 SmallVector
的静态容量。
② 底层存储:DynamicArray
的私有成员 data_
是一个 folly::SmallVector<T, N>
对象,用于实际存储元素。
③ 公共接口:DynamicArray
提供了 push_back()
、size()
、empty()
、operator[]
、at()
和 clear()
等公共成员函数,这些函数直接委托 (delegate) 调用 data_
对象的相应成员函数。例如,DynamicArray::push_back()
内部直接调用 data_.push_back()
。
④ 边界检查:DynamicArray
同时提供了无边界检查的 operator[]
和有边界检查的 at()
两种元素访问方式,与 SmallVector
保持一致。
⑤ main()
函数示例:main()
函数演示了 DynamicArray
的基本用法,包括创建对象、添加元素、访问元素、遍历元素和清空数组。可以看到,DynamicArray
的使用方式与 std::vector
非常相似,但底层使用了 SmallVector
,从而可能在小数据场景下获得性能优势。
总结:
通过实现 DynamicArray
类,我们展示了如何使用 SmallVector
作为底层容器来构建自定义的数据结构。DynamicArray
类封装了 SmallVector
的基本功能,并提供了类似于 std::vector
的接口。这个实战代码示例帮助我们更好地理解 SmallVector
的应用,以及如何利用其容量管理和元素操作 API 来实现动态数组的功能。在实际项目中,可以根据具体需求,基于 SmallVector
构建更复杂、更高效的数据结构。
2.4.2 最佳实践:选择合适的初始容量(Best Practice: Choosing the Right Initial Capacity)
SmallVector
的一个关键特性是其静态容量 N
,它决定了在栈上预留的空间大小。合理选择 N
的值对于充分发挥 SmallVector
的性能优势至关重要。如果 N
选择得当,可以最大限度地利用栈内存,避免堆内存分配,从而提高性能。反之,如果 N
选择不当,可能会导致栈空间浪费或频繁的堆内存分配,降低性能。本节将探讨如何根据不同的应用场景,选择合适的 SmallVector
静态容量 N
,并提供一些最佳实践建议。
影响因素:
选择合适的静态容量 N
需要考虑以下几个关键因素:
① 预期的元素数量:这是最主要的考虑因素。你需要预估 (estimate) SmallVector
在大多数情况下需要存储的元素数量范围。如果你的应用场景中,SmallVector
经常存储少量元素(例如,几个到几十个),那么设置一个较小的 N
值(例如,4、8、16、32)可能就足够了,这样可以充分利用栈内存的优势。如果元素数量波动较大,或者经常需要存储大量元素,那么可能需要权衡栈内存的使用和堆内存分配的开销。
② 栈内存限制:栈内存通常比堆内存小,且栈空间是有限的。过大的静态容量 N
会占用较多的栈空间,可能导致栈溢出 (stack overflow) 的风险,尤其是在递归调用或多线程环境下。因此,需要根据目标平台的栈内存大小限制,合理设置 N
的上限。通常,对于嵌入式系统或资源受限的环境,更需要谨慎控制栈内存的使用。
③ 元素类型的大小:元素类型 T
的大小也会影响栈内存的占用。如果 T
是一个较大的对象(例如,包含大量成员变量的类对象),那么即使 N
值较小,总的栈内存占用也可能比较大。反之,如果 T
是基本类型(如 int
、float
)或小型对象,则可以适当增大 N
值。
④ 性能需求:SmallVector
的主要优势在于小数据场景下的性能。如果你的应用对性能要求非常高,尤其是在元素数量较少的情况下,那么应该尽量选择合适的 N
值,以避免堆内存分配,最大化性能提升。如果性能不是首要考虑因素,或者主要处理大数据量,那么 N
的选择可以相对宽松一些。
最佳实践建议:
基于以上影响因素,以下是一些选择 SmallVector
静态容量 N
的最佳实践建议:
① 经验法则:对于大多数通用场景,可以考虑将 N
设置为 8、16 或 32。这些值通常能在栈内存占用和性能提升之间取得较好的平衡。例如,如果你的 SmallVector
主要用于存储少量配置参数、临时数据或小型数据结构,这些值通常是合适的。
② 基于统计分析:如果你的应用场景比较明确,可以进行统计分析 (statistical analysis),收集 SmallVector
实际存储元素数量的分布情况。例如,可以记录 SmallVector
的 size()
值的最大值、平均值、中位数等统计指标。根据这些统计数据,选择一个略大于平均值或中位数的 N
值,可以在大多数情况下避免堆内存分配,同时又不会过度占用栈空间。
③ 动态调整策略:在某些情况下,静态容量 N
可能无法满足所有场景的需求。可以考虑使用动态调整策略 (dynamic adjustment strategy)。例如,可以根据运行时的实际情况,动态地选择不同的 SmallVector
类型,或者在必要时切换到 std::vector
。但这会增加代码的复杂性,需要权衡利弊。
④ 基准测试:对于性能敏感的应用,基准测试 (benchmark) 是必不可少的。可以针对不同的 N
值进行性能测试,例如使用 Google Benchmark 等工具,测量不同 N
值下的性能表现(例如,插入、访问、遍历等操作的耗时)。通过基准测试,可以找到在特定场景下性能最优的 N
值。
⑤ 考虑元素类型:在选择 N
值时,务必考虑元素类型 T
的大小。如果 sizeof(T)
较大,则应适当减小 N
值,以控制栈内存占用。反之,如果 sizeof(T)
较小,则可以适当增大 N
值。
示例:
假设你正在开发一个网络服务器,需要处理客户端请求。每个请求可能包含少量参数,例如请求类型、请求 ID、时间戳等,这些参数可以存储在一个 SmallVector<std::string, N>
中。
⚝ 如果你预估大多数请求的参数数量在 5 个以内,可以将 N
设置为 8 或 16。
⚝ 如果你发现某些请求的参数数量可能会超过 32 个,但这种情况比较少见,可以考虑将 N
设置为 32,并在代码中处理超过静态容量的情况(例如,记录日志或使用 std::vector
替代)。
⚝ 如果你对性能要求非常高,可以进行基准测试,比较 N
为 8、16、32 等不同值时的请求处理速度,选择性能最优的 N
值。
总结:
选择合适的 SmallVector
静态容量 N
是一个需要权衡的过程,没有一个通用的最佳值,最佳的 N
值取决于具体的应用场景、预期的元素数量、栈内存限制、元素类型大小和性能需求等因素。通过合理的预估、统计分析、基准测试和动态调整策略,可以找到最适合特定场景的 N
值,充分发挥 SmallVector
的性能优势,并避免潜在的栈溢出风险。记住,没有银弹 (no silver bullet),最佳实践是根据实际情况进行权衡和选择。
2.5 类型安全与异常处理(Type Safety and Exception Handling)
SmallVector
作为 C++ 模板库的一部分,继承了 C++ 的类型安全 (type safety) 和异常处理 (exception handling) 机制。理解 SmallVector
的类型安全特性以及异常安全编程实践,有助于编写健壮、可靠的 C++ 代码。本节将探讨 SmallVector
的类型安全性,以及在使用 SmallVector
时如何进行异常处理,以确保程序的正确性和稳定性。
类型安全 (Type Safety):
SmallVector
的类型安全性主要体现在以下几个方面:
① 模板参数化:SmallVector
是一个模板类,通过模板参数 typename T
指定存储的元素类型。这意味着在编译时,SmallVector
只能存储指定类型的元素。如果尝试将其他类型的元素添加到 SmallVector
中,编译器会报错,从而在编译阶段就避免了类型错误。
1
SmallVector<int, 4> sv_int;
2
sv_int.push_back(10); // 正确:添加 int 类型元素
3
// sv_int.push_back("hello"); // 编译错误:类型不匹配,不能添加 string 类型元素
4
5
SmallVector<std::string, 4> sv_str;
6
sv_str.push_back("world"); // 正确:添加 string 类型元素
② 强类型接口:SmallVector
的成员函数,例如 push_back()
、insert()
、assign()
等,都要求传入的参数类型与 SmallVector
存储的元素类型 T
兼容。这进一步增强了类型安全性,避免了因类型不匹配导致的运行时错误。
③ 迭代器类型:SmallVector
的迭代器也是类型安全的。例如,SmallVector<int, 4>::iterator
只能用于遍历 SmallVector<int, 4>
类型的容器,并且解引用迭代器会得到 int&
类型的引用。这保证了迭代器操作的类型安全性。
④ 编译时检查:C++ 编译器会在编译时进行严格的类型检查,包括模板类型推导、函数参数类型匹配、迭代器类型检查等。这使得大部分类型错误可以在编译阶段被发现,而不是等到运行时才暴露出来,从而提高了代码的可靠性。
异常处理 (Exception Handling):
SmallVector
的设计考虑了异常安全,其大部分操作都提供了较强的异常安全保证。异常安全是指在程序发生异常时,程序仍然能够保持在一种有效 (valid) 的状态,不会发生资源泄漏或数据损坏。C++ 中通常将异常安全分为三个级别:
⚝ 不提供异常安全保证 (No exception safety):如果操作抛出异常,程序状态可能处于未知或不一致的状态,可能导致资源泄漏或数据损坏。
⚝ 基本异常安全保证 (Basic exception safety):如果操作抛出异常,程序状态不会损坏,所有对象仍然处于有效状态,不会发生资源泄漏。但程序状态可能与操作开始前不同。
⚝ 强异常安全保证 (Strong exception safety):如果操作抛出异常,程序状态保持不变,就像操作从未发生过一样。如果操作成功完成,则保证操作的完整性。
SmallVector
的大部分操作都提供了基本异常安全保证,部分操作甚至提供了强异常安全保证。以下是一些常见的 SmallVector
操作的异常安全级别:
① 构造函数:SmallVector
的拷贝构造函数和移动构造函数通常提供强异常安全保证。如果构造过程中抛出异常,源对象的状态保持不变。
② 赋值运算符:SmallVector
的拷贝赋值运算符和移动赋值运算符通常提供基本异常安全保证。如果赋值过程中抛出异常,目标对象可能处于有效但未指定的状态,但不会发生资源泄漏。
③ push_back()
和 emplace_back()
:这两个函数在插入元素时,如果需要动态分配内存,可能会抛出 std::bad_alloc
异常(内存分配失败)。它们通常提供基本异常安全保证。如果抛出异常,SmallVector
的状态仍然有效,但新元素可能没有被成功插入。
④ insert()
和 emplace()
:与 push_back()
和 emplace_back()
类似,insert()
和 emplace()
在插入元素时也可能抛出 std::bad_alloc
异常。它们通常也提供基本异常安全保证。
⑤ erase()
和 pop_back()
:这两个函数通常不会抛出异常,除非元素类型的拷贝/移动构造函数或析构函数抛出异常(如果元素是类对象)。在元素操作本身层面,它们是异常安全的。
⑥ at()
:at()
函数在索引越界时会抛出 std::out_of_range
异常。这是一种逻辑错误 (logic error) 导致的异常,用于指示程序错误使用了 at()
函数。
⑦ reserve()
和 shrink_to_fit()
:reserve()
在内存分配失败时可能抛出 std::bad_alloc
异常。shrink_to_fit()
通常不会抛出异常。
异常处理实践:
在使用 SmallVector
进行异常处理时,可以遵循以下实践:
① 使用 at()
进行边界检查:当你需要访问 SmallVector
中的元素,并且不确定索引是否有效时,应该使用 at()
函数,而不是 operator[]
。at()
会进行边界检查,并在越界时抛出 std::out_of_range
异常,你可以使用 try-catch
块捕获并处理这个异常。
1
SmallVector<int, 4> sv = {10, 20, 30};
2
try {
3
int element = sv.at(5); // 索引越界,抛出 std::out_of_range 异常
4
} catch (const std::out_of_range& e) {
5
std::cerr << "Index out of range: " << e.what() << std::endl;
6
// 处理越界异常的逻辑
7
}
② 处理内存分配异常:当使用 push_back()
、emplace_back()
、insert()
、emplace()
或 reserve()
等可能导致内存分配的操作时,应该考虑捕获 std::bad_alloc
异常,并进行适当的处理,例如释放已分配的资源、记录错误日志、或者优雅地终止程序。
1
SmallVector<int, 4> sv;
2
try {
3
for (int i = 0; i < 1000000; ++i) {
4
sv.push_back(i); // 可能抛出 std::bad_alloc 异常
5
}
6
} catch (const std::bad_alloc& e) {
7
std::cerr << "Memory allocation failed: " << e.what() << std::endl;
8
// 处理内存分配失败的逻辑
9
}
③ RAII 和异常安全资源管理:利用 RAII (Resource Acquisition Is Initialization) 原则,将资源封装在类中,并在类的构造函数中获取资源,析构函数中释放资源。这样可以确保在发生异常时,资源能够被正确释放,避免资源泄漏。SmallVector
本身就是一个 RAII 容器,它在析构函数中负责释放其管理的内存。
④ 避免在析构函数中抛出异常:C++ 规范建议不要在析构函数中抛出异常。如果析构函数中可能发生错误,应该在析构函数外部进行处理,或者设计成在析构函数中忽略错误。
总结:
SmallVector
提供了良好的类型安全性和基本异常安全保证。类型安全特性通过模板参数化和强类型接口在编译时避免类型错误。异常安全保证确保在发生异常时,程序状态仍然有效,不会发生资源泄漏。在使用 SmallVector
时,应该遵循异常处理的最佳实践,例如使用 at()
进行边界检查、处理内存分配异常、利用 RAII 进行资源管理等,以编写健壮、可靠的 C++ 代码。理解 SmallVector
的类型安全和异常处理特性,可以帮助开发者更好地利用 SmallVector
,并避免潜在的错误和风险。
END_OF_CHAPTER
3. chapter 3: SmallVector 进阶:深入原理与高级特性(SmallVector Advanced: In-depth Principles and Advanced Features)
3.1 内存分配策略:栈上空间与堆上空间(Memory Allocation Strategy: Stack Space and Heap Space):SmallVector 的内存管理机制深度剖析
SmallVector 最核心的特性之一,也是它区别于 std::vector
等动态数组容器的关键所在,就是其独特的内存分配策略。理解 SmallVector 的内存管理机制,是深入掌握其性能优势和适用场景的基础。本节将深入剖析 SmallVector 如何巧妙地利用栈上空间和堆上空间,实现高效的内存管理。
知识框架:
① 栈上空间(Stack Space):
▮▮▮▮ⓑ 概念:栈是一种后进先出(LIFO, Last-In-First-Out)的数据结构,由编译器自动管理,用于存储函数调用时的局部变量、函数参数、返回地址等。栈内存的分配和释放速度非常快,效率高。
▮▮▮▮ⓒ 特点:
▮▮▮▮▮▮▮▮❹ 自动管理:由编译器自动分配和释放,无需手动干预。
▮▮▮▮▮▮▮▮❺ 快速分配与释放:栈操作速度极快,效率高。
▮▮▮▮▮▮▮▮❻ 大小限制:栈空间通常有限,容易发生栈溢出(Stack Overflow)错误。
▮▮▮▮ⓖ SmallVector 的栈上使用:SmallVector 利用模板参数 N
指定的固定大小的栈上数组 buf_
来存储元素。当元素数量小于等于 N
时,所有元素都存储在栈上,避免了堆内存分配的开销。
② 堆上空间(Heap Space):
▮▮▮▮ⓑ 概念:堆是用于动态内存分配的区域,由程序员手动管理。使用 new
或 malloc
等操作在堆上分配内存,使用 delete
或 free
等操作释放内存。
▮▮▮▮ⓒ 特点:
▮▮▮▮▮▮▮▮❹ 手动管理:需要程序员显式地分配和释放内存,容易出现内存泄漏(Memory Leak)和悬 dangling 指针等问题。
▮▮▮▮▮▮▮▮❺ 灵活分配:堆空间较大,可以动态分配任意大小的内存。
▮▮▮▮▮▮▮▮❻ 分配与释放速度相对较慢:堆操作涉及复杂的内存管理算法,速度比栈操作慢。
▮▮▮▮ⓖ SmallVector 的堆上使用:当 SmallVector 存储的元素数量超过栈上空间 N
的限制时,它会像 std::vector
一样,在堆上动态分配内存来存储元素。
③ SmallVector 的内存管理机制:
▮▮▮▮ⓑ 混合策略:SmallVector 采用栈上空间优先,堆上空间备用的混合内存分配策略。
▮▮▮▮ⓒ 阈值 N
:模板参数 N
是一个关键的阈值。当元素数量小于等于 N
时,使用栈上 buf_
;当元素数量超过 N
时,动态分配堆内存。
▮▮▮▮ⓓ 动态扩容:当栈上空间不足且需要继续添加元素时,SmallVector 会自动将数据迁移到堆上,并进行动态扩容,类似于 std::vector
的扩容机制。
▮▮▮▮ⓔ 内存释放:当 SmallVector 对象销毁时,如果数据存储在堆上,则会释放堆内存;如果数据存储在栈上,则栈内存由编译器自动释放。
实战代码:
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
// 容量为 4 的 SmallVector
6
folly::SmallVector<int, 4> sv;
7
8
std::cout << "Initial capacity: " << sv.capacity() << std::endl; // 输出 4,栈上容量
9
10
// 添加 4 个元素,仍在栈上
11
for (int i = 0; i < 4; ++i) {
12
sv.push_back(i);
13
std::cout << "Size after push_back(" << i << "): " << sv.size() << ", capacity: " << sv.capacity() << std::endl;
14
}
15
std::cout << "Capacity after adding 4 elements: " << sv.capacity() << std::endl; // 仍然输出 4
16
17
// 添加第 5 个元素,触发堆内存分配
18
sv.push_back(4);
19
std::cout << "Size after push_back(4): " << sv.size() << ", capacity: " << sv.capacity() << std::endl; // capacity 变为大于 4 的值,例如 6,堆上容量
20
21
return 0;
22
}
代码解析:
⚝ folly::SmallVector<int, 4> sv;
:声明一个 SmallVector
,其栈上容量 N
为 4,元素类型为 int
。
⚝ 初始 capacity()
为 4,表明初始状态下,SmallVector 使用栈上空间。
⚝ 前 4 次 push_back()
操作,size()
增加,但 capacity()
保持不变,说明元素存储在栈上。
⚝ 第 5 次 push_back(4)
操作后,capacity()
显著增加,表明触发了堆内存分配,SmallVector 将数据迁移到了堆上,并进行了扩容。
高级应用:
⚝ 性能优化:对于已知元素数量通常较小且不超过栈上容量 N
的场景,使用 SmallVector 可以显著减少堆内存分配和释放的开销,提高程序性能,尤其是在对性能要求极高的场景,如嵌入式系统、实时系统、游戏开发等。
⚝ 减少内存碎片:由于栈内存的分配和释放是连续的,使用栈上空间可以减少堆内存碎片,提高内存利用率。
⚝ 避免堆分配失败:在内存资源受限的环境下,栈内存分配的成功率通常高于堆内存,使用 SmallVector 可以降低因堆分配失败而导致程序崩溃的风险。
API 全面解析:
⚝ SmallVector<T, N>
:模板类,T
是元素类型,N
是栈上容量。
⚝ capacity()
:返回当前 SmallVector 的容量。当数据存储在栈上时,返回栈上容量 N
;当数据存储在堆上时,返回堆上已分配的容量。
⚝ size()
:返回当前 SmallVector 中元素的数量。
总结:
SmallVector 的内存分配策略是其核心优势所在。通过巧妙地结合栈上空间和堆上空间,SmallVector 在保证动态数组功能的同时,尽可能地利用栈内存的快速分配和释放特性,从而在特定场景下实现更高的性能。理解 SmallVector 的内存管理机制,有助于我们更好地选择合适的容器,并充分发挥 SmallVector 的优势。
3.2 移动语义与性能优化(Move Semantics and Performance Optimization):利用移动语义减少拷贝开销,提升性能
移动语义(Move Semantics)是 C++11 引入的一项重要特性,旨在减少不必要的对象拷贝,从而提升程序性能。SmallVector 作为现代 C++ 容器,自然也充分利用了移动语义。本节将深入探讨 SmallVector 如何借助移动语义进行性能优化,以及如何在实际开发中应用这些技巧。
知识框架:
① 拷贝语义(Copy Semantics)的开销:
▮▮▮▮ⓑ 深拷贝与浅拷贝:对于包含动态分配内存的类(如 std::vector
, std::string
),拷贝操作通常需要进行深拷贝,即复制对象的所有数据,包括动态分配的内存。深拷贝开销较大。
▮▮▮▮ⓒ 不必要的拷贝:在很多情况下,对象的拷贝是不必要的,例如函数返回临时对象、对象赋值等。传统的拷贝语义会导致额外的性能开销。
② 移动语义(Move Semantics)的核心思想:
▮▮▮▮ⓑ 资源转移:移动语义的核心思想是将资源(如动态分配的内存)的所有权从一个对象转移到另一个对象,而不是进行深拷贝。
▮▮▮▮ⓒ 右值引用(Rvalue Reference):移动语义的实现依赖于右值引用 &&
。右值引用可以绑定到临时对象或即将销毁的对象(右值),从而可以安全地“窃取”其资源。
▮▮▮▮ⓓ 移动构造函数(Move Constructor)与移动赋值运算符(Move Assignment Operator):类需要定义移动构造函数和移动赋值运算符来实现移动语义。移动操作通常只需要进行浅拷贝,并将源对象的资源置为空或有效可析构状态。
③ SmallVector 与移动语义:
▮▮▮▮ⓑ 高效元素操作:SmallVector 的 push_back()
, emplace_back()
, insert()
, assign()
等修改容器内容的操作,都充分利用了移动语义。当插入或赋值的元素是右值时,会优先调用移动构造函数或移动赋值运算符,避免不必要的拷贝。
▮▮▮▮ⓒ 容器移动:SmallVector 自身也支持移动构造和移动赋值。移动 SmallVector 对象时,只会转移内部的指针和容量等信息,而不会复制元素数据,大幅提升移动效率。
▮▮▮▮ⓓ 返回值优化(Return Value Optimization, RVO)与移动语义的协同:编译器通常会进行返回值优化,直接在接收端构造返回值对象,结合移动语义,可以进一步减少拷贝开销。
实战代码:
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
class MyString {
5
public:
6
MyString(const char* str = "") {
7
std::cout << "Constructor called for: " << str << std::endl;
8
size_ = std::strlen(str);
9
data_ = new char[size_ + 1];
10
std::strcpy(data_, str);
11
}
12
MyString(const MyString& other) {
13
std::cout << "Copy constructor called for: " << other.data_ << std::endl;
14
size_ = other.size_;
15
data_ = new char[size_ + 1];
16
std::strcpy(data_, other.data_);
17
}
18
MyString(MyString&& other) noexcept {
19
std::cout << "Move constructor called for: " << other.data_ << std::endl;
20
size_ = other.size_;
21
data_ = other.data_;
22
other.data_ = nullptr; // 源对象置为有效可析构状态
23
other.size_ = 0;
24
}
25
~MyString() {
26
std::cout << "Destructor called for: ";
27
if (data_) {
28
std::cout << data_ << std::endl;
29
delete[] data_;
30
} else {
31
std::cout << "(moved-from)" << std::endl;
32
}
33
}
34
35
private:
36
char* data_;
37
size_t size_;
38
};
39
40
MyString getString() {
41
return MyString("Hello, SmallVector!"); // 返回临时对象,触发移动语义
42
}
43
44
int main() {
45
folly::SmallVector<MyString, 2> sv;
46
47
std::cout << "Pushing back a copy:" << std::endl;
48
MyString str1("String 1");
49
sv.push_back(str1); // 插入左值,调用拷贝构造函数
50
51
std::cout << "\nPushing back a move:" << std::endl;
52
sv.push_back(MyString("String 2")); // 插入右值,调用移动构造函数
53
54
std::cout << "\nPushing back a temporary object (getString()):" << std::endl;
55
sv.push_back(getString()); // 插入临时对象,调用移动构造函数 (RVO 可能发生)
56
57
std::cout << "\nEmplacing back:" << std::endl;
58
sv.emplace_back("String 3"); // 原位构造,避免拷贝和移动
59
60
return 0;
61
}
代码解析:
⚝ MyString
类:自定义的字符串类,带有拷贝构造函数、移动构造函数和析构函数,用于观察拷贝和移动行为。
⚝ getString()
函数:返回一个 MyString
临时对象(右值)。
⚝ sv.push_back(str1);
:插入左值 str1
,调用 MyString
的拷贝构造函数。
⚝ sv.push_back(MyString("String 2"));
:插入右值 MyString("String 2")
,调用 MyString
的移动构造函数。
⚝ sv.push_back(getString());
:插入 getString()
返回的临时对象,调用 MyString
的移动构造函数(编译器可能进行 RVO,直接在 SmallVector 内部构造对象,进一步优化)。
⚝ sv.emplace_back("String 3");
:使用 emplace_back()
原位构造 MyString
对象,避免了任何拷贝或移动操作。
高级应用:
⚝ 处理大型对象:当 SmallVector 存储的对象体积较大,拷贝开销显著时,移动语义的优势更加明显。例如,存储大型矩阵、图像数据等。
⚝ 函数返回值优化:在函数返回 SmallVector 对象时,利用移动语义可以避免昂贵的容器拷贝,提高函数调用的效率。
⚝ 算法优化:在使用 STL 算法(如 std::sort
, std::transform
)操作 SmallVector 时,移动语义可以减少元素交换和移动的开销。
API 全面解析:
⚝ push_back(const T& value)
:插入元素,如果 value
是左值,则调用拷贝构造函数;如果 value
是右值,则调用移动构造函数。
⚝ push_back(T&& value)
:强制移动插入元素,即使 value
是左值,也会尝试移动构造(需要 std::move
显式转换)。
⚝ emplace_back(Args&&... args)
:原位构造元素,避免拷贝和移动。
⚝ insert(iterator pos, const T& value)
/ insert(iterator pos, T&& value)
/ emplace(iterator pos, Args&&... args)
:插入元素到指定位置,同样支持拷贝、移动和原位构造。
⚝ assign(std::initializer_list<T> il)
/ assign(InputIterator first, InputIterator last)
/ assign(size_type count, const T& value)
/ assign(size_type count, T&& value)
:赋值操作,支持移动语义。
总结:
移动语义是现代 C++ 性能优化的重要手段。SmallVector 通过充分利用移动语义,在元素插入、容器移动等操作中,有效地减少了不必要的拷贝开销,提升了程序性能。在实际开发中,我们应该养成使用移动语义的习惯,例如尽可能使用右值、使用 std::move
显式移动、使用 emplace_back
等原位构造方法,以充分发挥 SmallVector 的性能优势。
3.3 emplace_back 与 in-place 构造(emplace_back and In-place Construction):高效元素插入方法,避免临时对象产生
emplace_back()
是 C++11 引入的容器成员函数,与 push_back()
类似,用于在容器尾部添加元素。但 emplace_back()
的独特之处在于它支持 in-place 构造(原位构造),可以直接在容器内部构造元素,避免了临时对象的产生,从而在某些情况下能够提供更高的性能。本节将深入解析 emplace_back()
的工作原理、优势以及在 SmallVector 中的应用。
知识框架:
① 临时对象的产生与开销:
▮▮▮▮ⓑ 临时对象的生命周期:临时对象通常在表达式结束后立即销毁。
▮▮▮▮ⓒ 拷贝与移动开销:使用 push_back()
插入元素时,通常需要先构造一个临时对象,然后将该临时对象拷贝或移动到容器内部。对于复杂对象,临时对象的构造、拷贝/移动和析构都会带来额外的开销。
② in-place 构造(原位构造)的概念:
▮▮▮▮ⓑ 直接在容器内存中构造:in-place 构造是指直接在容器预留的内存空间中构造对象,避免了先创建临时对象再拷贝/移动到容器内部的过程。
▮▮▮▮ⓒ 减少对象生命周期管理开销:由于避免了临时对象的产生,也减少了临时对象的构造和析构开销。
③ emplace_back()
的工作原理:
▮▮▮▮ⓑ 可变参数模板(Variadic Templates):emplace_back()
使用可变参数模板,可以接受任意数量和类型的参数。
▮▮▮▮ⓒ 完美转发(Perfect Forwarding):emplace_back()
使用完美转发将接收到的参数转发给元素类型的构造函数,在容器内部直接构造元素。
▮▮▮▮ⓓ 避免临时对象:通过 in-place 构造和完美转发,emplace_back()
避免了先创建临时对象再拷贝/移动到容器内部的过程。
④ emplace_back()
在 SmallVector 中的应用:
▮▮▮▮ⓑ 高效插入复杂对象:当 SmallVector 存储的对象类型构造复杂,或者拷贝/移动开销较大时,使用 emplace_back()
可以显著提升插入性能。
▮▮▮▮ⓒ 减少内存分配次数:在某些情况下,in-place 构造可以减少内存分配的次数,例如在 SmallVector 栈上空间充足时,emplace_back()
可以直接在栈上构造对象,避免堆内存分配。
实战代码:
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
class MyObject {
5
public:
6
MyObject(int a, double b, const std::string& c) : a_(a), b_(b), c_(c) {
7
std::cout << "Constructor called for MyObject(" << a << ", " << b << ", " << c << ")" << std::endl;
8
}
9
MyObject(const MyObject& other) : a_(other.a_), b_(other.b_), c_(other.c_) {
10
std::cout << "Copy constructor called for MyObject(" << a_ << ", " << b_ << ", " << c_ << ")" << std::endl;
11
}
12
MyObject(MyObject&& other) noexcept : a_(other.a_), b_(other.b_), c_(std::move(other.c_)) {
13
std::cout << "Move constructor called for MyObject(" << a_ << ", " << b_ << ", " << c_ << ")" << std::endl;
14
}
15
~MyObject() {
16
std::cout << "Destructor called for MyObject(" << a_ << ", " << b_ << ", " << c_ << ")" << std::endl;
17
}
18
19
private:
20
int a_;
21
double b_;
22
std::string c_;
23
};
24
25
int main() {
26
folly::SmallVector<MyObject, 2> sv;
27
28
std::cout << "Using push_back:" << std::endl;
29
sv.push_back(MyObject(1, 2.0, "push_back")); // 先构造临时对象,再移动到 SmallVector
30
31
std::cout << "\nUsing emplace_back:" << std::endl;
32
sv.emplace_back(2, 3.0, "emplace_back"); // 直接在 SmallVector 内部构造对象
33
34
return 0;
35
}
代码解析:
⚝ MyObject
类:自定义的类,构造函数、拷贝构造函数、移动构造函数和析构函数都带有输出信息,用于观察对象的构造和析构过程。
⚝ sv.push_back(MyObject(1, 2.0, "push_back"));
:使用 push_back()
插入元素,先在外部构造一个 MyObject
临时对象,然后将该临时对象移动到 SmallVector 内部。可以看到输出了 "Constructor", "Move constructor", "Destructor" 三个信息。
⚝ sv.emplace_back(2, 3.0, "emplace_back");
:使用 emplace_back()
插入元素,直接在 SmallVector 内部使用提供的参数 (2, 3.0, "emplace_back")
构造 MyObject
对象。可以看到只输出了 "Constructor" 和最终的 "Destructor" 信息,没有拷贝或移动构造函数的调用,表明避免了临时对象的产生。
高级应用:
⚝ 性能敏感场景:在对性能要求极高的场景,如游戏开发、高性能计算等,emplace_back()
可以作为一种重要的性能优化手段。
⚝ 避免不必要的拷贝:当元素类型的拷贝构造函数开销较大时,或者拷贝操作不必要时,应优先使用 emplace_back()
。
⚝ 构造复杂对象:对于构造函数参数较多的复杂对象,emplace_back()
可以使代码更简洁,避免手动创建临时对象。
API 全面解析:
⚝ emplace_back(Args&&... args)
:在 SmallVector 尾部原位构造一个元素。Args&&... args
是可变参数,会被完美转发给元素类型的构造函数。返回值类型为 reference
,指向新插入的元素。
总结:
emplace_back()
通过 in-place 构造和完美转发技术,提供了一种高效的元素插入方法,可以避免临时对象的产生,减少不必要的拷贝和移动开销,从而提升程序性能。在 SmallVector 中,emplace_back()
同样适用,尤其是在插入复杂对象或对性能有较高要求的场景下,应优先考虑使用 emplace_back()
来替代 push_back()
。
3.4 自定义分配器(Custom Allocator)的应用(Application of Custom Allocator):高级内存管理技巧,定制化内存分配策略
在 C++ 中,标准库容器(如 std::vector
, std::map
)允许用户自定义内存分配器(Allocator),以实现更精细的内存管理。SmallVector 同样支持自定义分配器。通过自定义分配器,我们可以根据特定应用场景的需求,定制内存分配策略,例如使用特定的内存池、共享内存、或者进行内存分配的性能监控和调试。本节将深入探讨自定义分配器的概念、实现方法以及在 SmallVector 中的应用。
知识框架:
① 默认分配器(Default Allocator):
▮▮▮▮ⓑ std::allocator
:C++ 标准库提供的默认分配器是 std::allocator
。它通常使用 ::operator new
和 ::operator delete
来进行内存的分配和释放。
▮▮▮▮ⓒ 适用场景:std::allocator
适用于大多数通用场景,但在某些特殊场景下可能不是最优选择。
② 自定义分配器的需求:
▮▮▮▮ⓑ 性能优化:在高性能应用中,默认分配器的性能可能成为瓶颈。自定义分配器可以使用更高效的内存池、预分配等策略来提升性能。
▮▮▮▮ⓒ 内存控制:在资源受限的环境下,需要更精细地控制内存分配,例如限制内存使用量、使用特定的内存区域等。
▮▮▮▮ⓓ 内存监控与调试:自定义分配器可以方便地添加内存分配的监控和调试功能,例如记录内存分配日志、检测内存泄漏等。
▮▮▮▮ⓔ 特殊内存区域:例如使用共享内存、持久化内存等特殊内存区域。
③ 自定义分配器的实现:
▮▮▮▮ⓑ Allocator 概念:C++ 标准库对 Allocator 有一套严格的接口要求。自定义分配器需要满足这些接口要求,才能与标准库容器兼容。
▮▮▮▮ⓒ 必须提供的类型和成员函数:
▮▮▮▮▮▮▮▮❹ value_type
:分配器分配的内存存储的对象类型。
▮▮▮▮▮▮▮▮❺ pointer
, const_pointer
, reference
, const_reference
:指针和引用的类型定义。
▮▮▮▮▮▮▮▮❻ allocate(size_t n)
:分配 n
个 value_type
对象所需的内存。
▮▮▮▮▮▮▮▮❼ deallocate(pointer p, size_t n)
:释放之前分配的 n
个 value_type
对象的内存。
▮▮▮▮▮▮▮▮❽ construct(pointer p, const T& value)
/ construct(pointer p, T&& value)
:在已分配的内存 p
上构造对象。
▮▮▮▮▮▮▮▮❾ destroy(pointer p)
:销毁 p
指向的对象。
▮▮▮▮ⓙ 可选提供的类型和成员函数:例如 max_size()
, address()
, operator==
, operator!=
等。
▮▮▮▮ⓚ 状态与有状态分配器/无状态分配器:分配器可以是有状态的或无状态的。无状态分配器可以自由复制,有状态分配器则需要考虑状态的拷贝和共享。
④ SmallVector 与自定义分配器:
▮▮▮▮ⓑ 模板参数:SmallVector 的模板定义允许指定自定义分配器类型:folly::SmallVector<T, N, Allocator>
,其中 Allocator
就是自定义分配器类型。
▮▮▮▮ⓒ 使用方法:在声明 SmallVector 对象时,可以传入自定义分配器实例。SmallVector 的内存分配和释放操作将委托给自定义分配器。
实战代码:
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
#include <memory>
4
5
// 自定义分配器示例:简单的计数分配器
6
template <typename T>
7
class CountingAllocator {
8
public:
9
using value_type = T;
10
CountingAllocator() noexcept : allocated_count_(0), deallocated_count_(0) {}
11
template <typename U> CountingAllocator(const CountingAllocator<U>&) noexcept : allocated_count_(0), deallocated_count_(0) {} // 允许从其他类型的 CountingAllocator 转换
12
13
T* allocate(std::size_t n) {
14
std::cout << "Allocating " << n * sizeof(T) << " bytes" << std::endl;
15
allocated_count_++;
16
return static_cast<T*>(std::malloc(n * sizeof(T)));
17
}
18
void deallocate(T* p, std::size_t n) noexcept {
19
std::cout << "Deallocating " << n * sizeof(T) << " bytes" << std::endl;
20
deallocated_count_++;
21
std::free(p);
22
}
23
24
template <typename U, typename... Args>
25
void construct(U* p, Args&&... args) {
26
std::cout << "Constructing object at " << p << std::endl;
27
::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
28
}
29
30
void destroy(T* p) noexcept {
31
std::cout << "Destroying object at " << p << std::endl;
32
p->~T();
33
}
34
35
int getAllocatedCount() const { return allocated_count_; }
36
int getDeallocatedCount() const { return deallocated_count_; }
37
38
private:
39
int allocated_count_;
40
int deallocated_count_;
41
};
42
43
template <typename T, typename U>
44
bool operator==(const CountingAllocator<T>&, const CountingAllocator<U>&) { return true; } // 无状态分配器,始终相等
45
template <typename T, typename U>
46
bool operator!=(const CountingAllocator<T>&, const CountingAllocator<U>&) { return false; }
47
48
int main() {
49
CountingAllocator<int> allocator;
50
folly::SmallVector<int, 2, CountingAllocator<int>> sv(allocator);
51
52
sv.push_back(1);
53
sv.push_back(2);
54
sv.push_back(3); // 触发堆内存分配
55
56
std::cout << "Allocated count: " << allocator.getAllocatedCount() << std::endl;
57
std::cout << "Deallocated count: " << allocator.getDeallocatedCount() << std::endl;
58
59
return 0;
60
}
代码解析:
⚝ CountingAllocator
类:自定义的计数分配器,用于统计内存分配和释放的次数,并在分配和释放内存时输出信息。
⚝ allocate(size_t n)
:使用 std::malloc
分配内存,并增加分配计数。
⚝ deallocate(T* p, size_t n)
:使用 std::free
释放内存,并增加释放计数。
⚝ construct(U* p, Args&&... args)
:使用 placement new 在已分配的内存上构造对象。
⚝ destroy(T* p)
:显式调用对象的析构函数。
⚝ operator==
和 operator!=
:对于无状态分配器,通常返回 true
和 false
。
⚝ folly::SmallVector<int, 2, CountingAllocator<int>> sv(allocator);
:声明 SmallVector 时,指定使用 CountingAllocator<int>
作为分配器类型,并传入分配器实例 allocator
。
高级应用:
⚝ 内存池分配器:使用预先分配好的内存池来管理内存,减少动态内存分配的开销,提高性能。
⚝ 共享内存分配器:在多进程环境中,使用共享内存分配器可以让多个进程共享 SmallVector 容器的数据。
⚝ 调试分配器:用于内存泄漏检测、内存越界访问检测等调试目的。
⚝ 定制化内存策略:例如,根据对象的大小选择不同的内存分配策略,或者使用 NUMA 感知的内存分配器。
API 全面解析:
⚝ folly::SmallVector<T, N, Allocator = std::allocator<T>>
:SmallVector 模板类的定义,第三个模板参数 Allocator
用于指定分配器类型,默认为 std::allocator<T>
。
⚝ 构造函数:SmallVector 的构造函数可以接受一个分配器实例作为参数,用于指定容器使用的分配器。
⚝ get_allocator()
:返回容器当前使用的分配器实例。
总结:
自定义分配器是 C++ 中高级内存管理的重要技术。SmallVector 支持自定义分配器,为用户提供了更大的灵活性和控制权,可以根据具体的应用场景定制内存分配策略,实现性能优化、内存控制、监控调试等目标。掌握自定义分配器的使用方法,可以帮助我们更好地管理内存资源,构建更高效、更可靠的 C++ 程序。
3.5 与 std::vector 的性能对比评测(Performance Comparison with std::vector):基准测试与性能分析,量化 SmallVector 的优势
SmallVector 的设计目标之一就是在特定场景下提供优于 std::vector
的性能。本节将通过基准测试(Benchmark)的方法,对比 SmallVector 和 std::vector
在不同操作下的性能差异,量化 SmallVector 的性能优势,并分析其背后的原因。
知识框架:
① 性能评测的重要性:
▮▮▮▮ⓑ 选择合适的容器:性能评测可以帮助我们了解不同容器的性能特点,从而根据实际应用场景选择最合适的容器。
▮▮▮▮ⓒ 优化代码性能:通过性能评测,可以发现代码中的性能瓶颈,并针对性地进行优化。
▮▮▮▮ⓓ 量化性能提升:性能评测可以量化优化措施带来的性能提升效果,为优化决策提供数据支持。
② 基准测试(Benchmark)方法:
▮▮▮▮ⓑ 选择合适的测试工具:常用的 C++ 基准测试工具有 Google Benchmark, Criterion, Hayai 等。
▮▮▮▮ⓒ 设计合理的测试用例:测试用例应尽可能覆盖容器的常用操作,例如插入、删除、访问、遍历等,并考虑不同数据规模和场景。
▮▮▮▮ⓓ 避免测试误差:需要注意测试环境的稳定性,避免其他因素干扰测试结果。多次运行取平均值可以减少随机误差。
▮▮▮▮ⓔ 性能指标:常用的性能指标包括执行时间、CPU 周期数、内存分配次数等。
③ SmallVector vs. std::vector 的性能差异分析:
▮▮▮▮ⓑ 栈上空间优势:当元素数量小于等于 SmallVector 的栈上容量 N
时,SmallVector 的所有操作都在栈上进行,避免了堆内存分配和释放的开销,速度更快。
▮▮▮▮ⓒ 堆上扩容开销:当元素数量超过 N
时,SmallVector 会在堆上分配内存,并进行动态扩容,此时性能与 std::vector
接近,甚至可能略有下降(因为可能存在数据从栈到堆的迁移开销)。
▮▮▮▮ⓓ 移动语义优势:SmallVector 和 std::vector
都支持移动语义,但在某些情况下,SmallVector 的栈上存储可能更有利于编译器进行优化,例如返回值优化(RVO)。
▮▮▮▮ⓔ 适用场景:SmallVector 在元素数量较小且可预测的场景下性能优势明显,例如存储固定大小的小数组、函数参数传递等。std::vector
更适用于元素数量不确定或可能很大的场景。
④ 性能评测关注点:
▮▮▮▮ⓑ 不同操作的性能对比:例如,push_back()
, insert()
, operator[]
, 遍历等操作的性能对比。
▮▮▮▮ⓒ 不同数据规模的影响:测试不同元素数量下 SmallVector 和 std::vector
的性能差异。
▮▮▮▮ⓓ 不同元素类型的影响:测试存储不同类型(例如,POD 类型、复杂对象)时的性能差异。
▮▮▮▮ⓔ 编译优化选项的影响:不同的编译优化选项可能会影响性能测试结果。
3.5.1 benchmark 代码示例(benchmark Code Example):使用 Google Benchmark 或其他工具进行性能测试
以下示例代码使用 Google Benchmark 框架进行 SmallVector 和 std::vector
的性能对比测试。
1
#include <folly/container/SmallVector.h>
2
#include <vector>
3
#include <benchmark/benchmark.h>
4
5
// Benchmark for push_back operation with small size (within stack capacity)
6
static void BM_SmallVector_PushBack_Small(benchmark::State& state) {
7
for (auto _ : state) {
8
folly::SmallVector<int, 8> sv;
9
for (int i = 0; i < state.range(0); ++i) {
10
sv.push_back(i);
11
}
12
}
13
}
14
BENCHMARK(BM_SmallVector_PushBack_Small)->RangeMultiplier(2)->Range(1, 8);
15
16
static void BM_StdVector_PushBack_Small(benchmark::State& state) {
17
for (auto _ : state) {
18
std::vector<int> v;
19
for (int i = 0; i < state.range(0); ++i) {
20
v.push_back(i);
21
}
22
}
23
}
24
BENCHMARK(BM_StdVector_PushBack_Small)->RangeMultiplier(2)->Range(1, 8);
25
26
// Benchmark for push_back operation with large size (exceeding stack capacity)
27
static void BM_SmallVector_PushBack_Large(benchmark::State& state) {
28
for (auto _ : state) {
29
folly::SmallVector<int, 8> sv;
30
for (int i = 0; i < state.range(0); ++i) {
31
sv.push_back(i);
32
}
33
}
34
}
35
BENCHMARK(BM_SmallVector_PushBack_Large)->RangeMultiplier(10)->Range(10, 10000);
36
37
static void BM_StdVector_PushBack_Large(benchmark::State& state) {
38
for (auto _ : state) {
39
std::vector<int> v;
40
for (int i = 0; i < state.range(0); ++i) {
41
v.push_back(i);
42
}
43
}
44
}
45
BENCHMARK(BM_StdVector_PushBack_Large)->RangeMultiplier(10)->Range(10, 10000);
46
47
// Benchmark for element access (operator[])
48
static void BM_SmallVector_Access(benchmark::State& state) {
49
folly::SmallVector<int, 10000> sv;
50
for (int i = 0; i < 10000; ++i) {
51
sv.push_back(i);
52
}
53
for (auto _ : state) {
54
for (int i = 0; i < 10000; ++i) {
55
benchmark::DoNotOptimize(sv[i]); // Prevent compiler optimization
56
}
57
}
58
}
59
BENCHMARK(BM_SmallVector_Access);
60
61
static void BM_StdVector_Access(benchmark::State& state) {
62
std::vector<int> v;
63
for (int i = 0; i < 10000; ++i) {
64
v.push_back(i);
65
}
66
for (auto _ : state) {
67
for (int i = 0; i < 10000; ++i) {
68
benchmark::DoNotOptimize(v[i]); // Prevent compiler optimization
69
}
70
}
71
}
72
BENCHMARK(BM_StdVector_Access);
73
74
75
int main(int argc, char** argv) {
76
benchmark::Initialize(&argc, argv);
77
if (benchmark::ReportUnrecognizedArguments(argc, argv)) return 1;
78
benchmark::RunSpecifiedBenchmarks();
79
return 0;
80
}
代码解析:
⚝ 引入头文件:folly/container/SmallVector.h
, vector
, benchmark/benchmark.h
。
⚝ BM_SmallVector_PushBack_Small
和 BM_StdVector_PushBack_Small
:测试小尺寸(1-8 个元素) push_back()
操作的性能。SmallVector 的栈上容量设置为 8。
⚝ BM_SmallVector_PushBack_Large
和 BM_StdVector_PushBack_Large
:测试大尺寸(10-10000 个元素) push_back()
操作的性能,超出 SmallVector 的栈上容量。
⚝ BM_SmallVector_Access
和 BM_StdVector_Access
:测试元素访问 operator[]
的性能。
⚝ BENCHMARK(...)->RangeMultiplier(..)->Range(..)
:Google Benchmark 的宏,用于定义基准测试函数和参数范围。
⚝ benchmark::DoNotOptimize(...)
:防止编译器过度优化,确保测试代码被实际执行。
⚝ main()
函数:初始化 Google Benchmark 并运行指定的基准测试。
运行与结果分析:
编译并运行上述 benchmark 代码,可以得到 SmallVector 和 std::vector
在不同操作下的性能数据。通常情况下,可以观察到:
⚝ 小尺寸 push_back()
操作:SmallVector 性能明显优于 std::vector
,因为 SmallVector 使用栈上空间,避免了堆内存分配。
⚝ 大尺寸 push_back()
操作:SmallVector 和 std::vector
性能接近,但 SmallVector 可能略慢,因为存在栈到堆的数据迁移开销。
⚝ 元素访问 operator[]
:SmallVector 和 std::vector
性能非常接近,因为都是直接内存访问。
总结:
通过基准测试,我们可以量化 SmallVector 在特定场景下的性能优势。SmallVector 在元素数量较小且不超过栈上容量时,由于栈上内存分配的优势,性能明显优于 std::vector
。在元素数量较大时,两者性能接近。因此,在选择容器时,需要根据实际应用场景的元素数量特点,权衡 SmallVector 和 std::vector
的优缺点,选择最合适的容器。对于元素数量通常较小且可预测的场景,SmallVector 是一个优秀的性能优化选择。
END_OF_CHAPTER
4. chapter 4: SmallVector 高级应用与实战案例(SmallVector Advanced Applications and Practical Cases)
4.1 在高性能计算中的应用(Applications in High-Performance Computing)
高性能计算(High-Performance Computing, HPC)领域对性能有着极致的追求,每一个细微的优化都可能对整体计算效率产生显著影响。SmallVector
因其独特的设计理念,在 HPC 场景中能够发挥重要作用,尤其是在处理数据规模相对较小且对性能敏感的计算任务时。
① 数据局部性与缓存优化:在 HPC 应用中,数据局部性是影响性能的关键因素之一。SmallVector
的栈上内存分配特性,使得数据更有可能被分配在连续的内存空间中,从而提高了缓存命中率。对于频繁访问的小型数据集,这可以显著减少内存访问延迟,提升计算速度。
② 减少动态内存分配开销:HPC 应用通常包含大量的循环和迭代计算,频繁的动态内存分配和释放会带来显著的性能开销。SmallVector
的固定容量优化,允许在栈上预分配一定大小的内存,避免了在数据规模较小时的堆内存分配,从而减少了动态内存管理的开销。
③ 适用于小型数据集和临时数据结构:许多 HPC 算法在执行过程中会产生大量的小型数据集或临时数据结构,例如:
⚝ 线性代数运算中的小型向量和矩阵。
⚝ 数值模拟中的局部网格数据。
⚝ 粒子物理模拟中的粒子信息集合。
⚝ 信号处理中的小段数据帧。
对于这些场景,SmallVector
可以作为 std::vector
的高效替代品。当数据量不超过 SmallVector
的栈上容量时,其性能优势尤为明显。
④ 实战代码:使用 SmallVector 加速向量点积运算
假设我们需要频繁计算两个向量的点积,向量长度通常较小。我们可以使用 SmallVector
来存储向量数据,并比较其与 std::vector
在性能上的差异。
1
#include <vector>
2
#include <numeric>
3
#include <chrono>
4
#include <iostream>
5
#include <folly/container/SmallVector.h>
6
7
using namespace std;
8
using namespace folly;
9
10
double dot_product_std_vector(const vector<double>& a, const vector<double>& b) {
11
return inner_product(a.begin(), a.end(), b.begin(), 0.0);
12
}
13
14
double dot_product_small_vector(const SmallVector<double>& a, const SmallVector<double>& b) {
15
return inner_product(a.begin(), a.end(), b.begin(), 0.0);
16
}
17
18
int main() {
19
int size = 10; // 向量长度较小
20
int iterations = 1000000; // 迭代次数
21
22
vector<double> std_vec_a(size, 1.0);
23
vector<double> std_vec_b(size, 2.0);
24
SmallVector<double> small_vec_a(size, 1.0);
25
SmallVector<double> small_vec_b(size, 2.0);
26
27
// std::vector 性能测试
28
auto start_std = chrono::high_resolution_clock::now();
29
for (int i = 0; i < iterations; ++i) {
30
dot_product_std_vector(std_vec_a, std_vec_b);
31
}
32
auto end_std = chrono::high_resolution_clock::now();
33
chrono::duration<double> duration_std = end_std - start_std;
34
cout << "std::vector dot product time: " << duration_std.count() << " seconds" << endl;
35
36
// SmallVector 性能测试
37
auto start_small = chrono::high_resolution_clock::now();
38
for (int i = 0; i < iterations; ++i) {
39
dot_product_small_vector(small_vec_a, small_vec_b);
40
}
41
auto end_small = chrono::high_resolution_clock::now();
42
chrono::duration<double> duration_small = end_small - start_small;
43
cout << "SmallVector dot product time: " << duration_small.count() << " seconds" << endl;
44
45
return 0;
46
}
在这个示例中,我们分别使用 std::vector
和 SmallVector
进行了向量点积运算的性能测试。在向量长度较小且迭代次数较多的情况下,SmallVector
通常会表现出更优的性能,这主要是由于其栈上分配减少了动态内存分配的开销。
⑤ 总结:在 HPC 领域,SmallVector
适用于对性能要求苛刻,且数据规模相对较小的场景。通过合理利用 SmallVector
,可以有效地提升 HPC 应用的计算效率。
4.2 在网络编程中的应用(Applications in Network Programming)
网络编程中,数据包的处理是核心任务之一。网络数据包通常具有大小受限且生命周期短暂的特点,这使得 SmallVector
在网络编程领域拥有独特的优势。
① 高效的网络数据包缓冲区:网络数据包的大小通常不会太大,例如以太网帧的最大传输单元(Maximum Transmission Unit, MTU)通常在 1500 字节左右。SmallVector
可以作为高效的网络数据包缓冲区,利用其栈上空间存储数据包内容。
② 减少网络数据包处理延迟:在高性能网络服务中,降低数据包处理延迟至关重要。SmallVector
避免了堆内存分配,减少了内存分配和垃圾回收的开销,从而降低了网络数据包的处理延迟。
③ 适用于协议解析和数据包构建:网络协议解析和数据包构建过程中,经常需要处理和操作小型的字节序列。SmallVector
提供了方便的 API 和高效的内存管理,非常适合用于实现协议解析器和数据包构建器。
④ 实战代码:使用 SmallVector 实现简单的网络数据包处理
假设我们需要处理简单的网络数据包,数据包结构包含包头和包体,包头固定大小,包体大小可变但通常较小。我们可以使用 SmallVector
来存储整个数据包。
1
#include <iostream>
2
#include <vector>
3
#include <folly/container/SmallVector.h>
4
5
using namespace std;
6
using namespace folly;
7
8
// 模拟网络数据包结构
9
struct NetworkPacket {
10
SmallVector<uint8_t> data;
11
12
NetworkPacket(size_t header_size, const uint8_t* payload, size_t payload_size) {
13
data.resize(header_size + payload_size);
14
// 填充包头 (示例,实际应用中需要根据协议填充)
15
for (size_t i = 0; i < header_size; ++i) {
16
data[i] = static_cast<uint8_t>(i);
17
}
18
// 拷贝 payload
19
memcpy(data.data() + header_size, payload, payload_size);
20
}
21
22
void process_packet() {
23
cout << "Processing packet, size: " << data.size() << endl;
24
// 实际的网络包处理逻辑...
25
}
26
};
27
28
int main() {
29
uint8_t payload[] = {0x01, 0x02, 0x03, 0x04, 0x05};
30
NetworkPacket packet(10, payload, sizeof(payload));
31
packet.process_packet();
32
33
return 0;
34
}
在这个示例中,NetworkPacket
结构体使用 SmallVector<uint8_t>
来存储网络数据包。由于网络数据包的大小通常有限,SmallVector
可以有效地管理数据包缓冲区,并减少内存分配开销。
⑤ 总结:在网络编程领域,SmallVector
尤其适用于处理网络数据包这种小而频繁的数据。它可以作为高效的网络数据包缓冲区,降低处理延迟,提升网络服务的性能。
4.3 在游戏开发中的应用(Applications in Game Development)
游戏开发对性能的要求极高,尤其是在需要实时渲染和高速响应的游戏引擎中。SmallVector
在游戏开发中可以用于优化各种数据结构,提升游戏性能和帧率。
① 游戏对象组件管理:现代游戏引擎通常采用组件式架构(Component-Based Architecture),游戏对象由多个组件构成。对于组件数量较少的游戏对象,可以使用 SmallVector
来存储组件列表。
② 粒子系统:粒子系统是游戏中常用的特效技术,每个粒子通常包含少量属性(位置、速度、颜色等)。使用 SmallVector
可以高效地存储和管理粒子的属性数据。
③ 小型游戏对象和临时数据:游戏中存在大量小型游戏对象和临时数据,例如:
⚝ 碰撞检测中的碰撞点列表。
⚝ 渲染过程中的顶点索引列表。
⚝ 游戏逻辑中的临时状态数据。
对于这些小型数据集合,SmallVector
可以提供比 std::vector
更高效的内存管理和访问速度。
④ 实战代码:使用 SmallVector 优化游戏对象组件管理
假设我们有一个简单的游戏引擎,游戏对象由组件构成,每个游戏对象最多包含少量组件。我们可以使用 SmallVector
来存储游戏对象的组件列表。
1
#include <iostream>
2
#include <vector>
3
#include <folly/container/SmallVector.h>
4
5
using namespace std;
6
using namespace folly;
7
8
// 游戏组件基类
9
class GameComponent {
10
public:
11
virtual ~GameComponent() = default;
12
virtual void update() = 0;
13
};
14
15
// 示例组件:位置组件
16
class PositionComponent : public GameComponent {
17
public:
18
PositionComponent(float x, float y) : x_(x), y_(y) {}
19
void update() override {
20
cout << "Position: (" << x_ << ", " << y_ << ")" << endl;
21
}
22
private:
23
float x_;
24
float y_;
25
};
26
27
// 示例组件:渲染组件
28
class RenderComponent : public GameComponent {
29
public:
30
RenderComponent(const string& model_name) : model_name_(model_name) {}
31
void update() override {
32
cout << "Rendering model: " << model_name_ << endl;
33
}
34
private:
35
string model_name_;
36
};
37
38
// 游戏对象类
39
class GameObject {
40
public:
41
using ComponentList = SmallVector<unique_ptr<GameComponent>>; // 使用 SmallVector 存储组件
42
ComponentList components;
43
44
void addComponent(unique_ptr<GameComponent> component) {
45
components.push_back(move(component));
46
}
47
48
void updateComponents() {
49
for (const auto& component : components) {
50
component->update();
51
}
52
}
53
};
54
55
int main() {
56
GameObject obj;
57
obj.addComponent(make_unique<PositionComponent>(10.0f, 20.0f));
58
obj.addComponent(make_unique<RenderComponent>("player_model"));
59
60
obj.updateComponents();
61
62
return 0;
63
}
在这个示例中,GameObject
类使用 SmallVector<unique_ptr<GameComponent>>
来存储组件列表。由于游戏对象通常组件数量有限,SmallVector
可以有效地管理组件,并减少内存分配开销,从而提升游戏性能。
⑤ 总结:在游戏开发中,SmallVector
可以用于优化游戏引擎的各种数据结构,尤其是在处理组件管理、粒子系统和小型游戏对象等场景时,能够提升游戏性能和帧率,为玩家带来更流畅的游戏体验。
4.4 与其他 Folly 组件的协同工作(Collaboration with Other Folly Components)
Folly
库作为一个全面的 C++ 库,提供了许多高效且实用的组件。SmallVector
可以与其他 Folly
组件协同工作,构建更强大、更高效的应用程序。
① 与 FBString
的协同:FBString
是 Folly
提供的字符串类,它在小字符串优化方面与 SmallVector
有相似的设计理念。SmallVector<FBString>
可以用于存储字符串集合,结合两者的优化特性,可以高效地处理字符串数据。例如,在文本处理、日志分析等场景中,可以使用 SmallVector<FBString>
来存储和操作文本行或日志条目。
② 与 FBVector
的协同:FBVector
是 Folly
提供的另一个动态数组容器,它在某些方面比 std::vector
更加高效。SmallVector
和 FBVector
可以根据不同的场景和需求进行选择和组合使用。例如,可以使用 SmallVector
存储小型数据集,当数据规模超过 SmallVector
的容量时,可以将数据转移到 FBVector
中进行处理。
③ 与 Folly Algorithms
的协同:Folly
提供了丰富的算法库,包括排序、查找、变换等。SmallVector
可以与 Folly Algorithms
中的算法无缝集成,利用这些高效的算法对 SmallVector
中的数据进行处理。例如,可以使用 folly::sort
对 SmallVector
中的元素进行排序,使用 folly::binary_search
在 SmallVector
中查找元素。
④ 与 Futures/Promises
的协同:在异步编程中,Futures/Promises
是常用的并发编程模型。SmallVector
可以用于在异步任务之间传递数据。例如,一个异步任务可以使用 SmallVector
存储计算结果,并将 SmallVector
作为 Future
的返回值传递给后续的任务。
⑤ 实战代码:使用 SmallVector<FBString>
存储和处理文本行
假设我们需要读取一个文本文件,并将每行文本存储到容器中进行处理。我们可以使用 SmallVector<FBString>
来存储文本行。
1
#include <iostream>
2
#include <fstream>
3
#include <string>
4
#include <folly/container/SmallVector.h>
5
#include <folly/FBString.h>
6
7
using namespace std;
8
using namespace folly;
9
10
int main() {
11
ifstream file("input.txt");
12
if (!file.is_open()) {
13
cerr << "Error opening file" << endl;
14
return 1;
15
}
16
17
SmallVector<FBString> lines;
18
string line;
19
while (getline(file, line)) {
20
lines.emplace_back(line); // 使用 emplace_back 直接构造 FBString
21
}
22
file.close();
23
24
cout << "Read " << lines.size() << " lines from file." << endl;
25
26
// 遍历并处理文本行
27
for (const auto& fb_line : lines) {
28
cout << "Line: " << fb_line.toStdString() << endl; // 转换为 std::string 输出
29
// 实际的文本行处理逻辑...
30
}
31
32
return 0;
33
}
在这个示例中,我们使用 SmallVector<FBString>
存储从文件中读取的文本行。FBString
的小字符串优化和 SmallVector
的栈上分配特性结合,可以高效地处理文本数据。
⑥ 总结:SmallVector
可以与 Folly
库中的其他组件,如 FBString
, FBVector
, Folly Algorithms
, Futures/Promises
等协同工作,构建更强大、更高效的应用程序。通过合理利用 Folly
库的组件,可以充分发挥 SmallVector
的优势,提升程序性能和开发效率。
4.5 案例分析:开源项目中的 SmallVector 应用(Case Study: SmallVector Applications in Open Source Projects)
学习开源项目如何使用 SmallVector
是深入理解其应用场景和最佳实践的有效途径。许多知名的开源项目,尤其是在高性能计算、网络编程和游戏开发领域,都采用了 Folly
库,并从中受益。
① 查找开源项目中的 SmallVector 用例:可以通过 GitHub、GitLab 等代码托管平台搜索使用 Folly
和 SmallVector
的开源项目。关键词可以包括 "folly", "SmallVector", "facebook/folly" 等。
② 分析项目代码:选择一些具有代表性的开源项目,分析其代码库中 SmallVector
的使用方式。重点关注以下几个方面:
⚝ SmallVector
的应用场景:项目在哪些模块或功能中使用了 SmallVector
?
⚝ SmallVector
的容量选择:项目如何选择 SmallVector
的容量参数?
⚝ SmallVector
的性能收益:项目是否在文档或代码注释中提及使用 SmallVector
带来的性能提升?
⚝ SmallVector
与其他 Folly
组件的协同:项目是否将 SmallVector
与其他 Folly
组件结合使用?
③ 案例示例:folly itself
Folly
库自身就大量使用了 SmallVector
。在 Folly
的源代码中搜索 folly::SmallVector
,可以找到大量的用例。例如,在 Folly
的 FBString
实现中,SmallVector
被用于存储小字符串的字符数据,实现了小字符串优化。
1
// 摘自 folly/FBString.h (简化版本)
2
template <typename CharType, size_t N>
3
class SmallString {
4
private:
5
SmallVector<CharType, N> data_; // 使用 SmallVector 存储小字符串数据
6
size_t size_;
7
public:
8
// ...
9
};
在 FBString
的实现中,SmallVector
作为内部数据存储结构,充分利用了 SmallVector
的栈上分配和固定容量优化特性,提升了小字符串的处理效率。
④ 案例示例:ClickHouse
ClickHouse 是一个开源的列式数据库管理系统,以其高性能而闻名。ClickHouse 也使用了 Folly
库,并在其代码库中使用了 SmallVector
。例如,在 ClickHouse 的某些数据结构和算法实现中,SmallVector
被用于存储小型数据集合,以提升性能。
⑤ 案例分析总结:通过分析开源项目中的 SmallVector
用例,我们可以学习到:
⚝ SmallVector
在高性能场景中的广泛应用。
⚝ 如何根据实际场景选择合适的 SmallVector
容量。
⚝ SmallVector
与其他库或组件协同工作的模式。
⚝ 开源项目对性能优化的极致追求和实践经验。
学习开源项目的实践经验,可以帮助我们更好地理解和应用 SmallVector
,并在自己的项目中充分发挥其优势。
END_OF_CHAPTER
5. chapter 5: SmallVector API 全面解析(SmallVector API Comprehensive Analysis)
本章将深入探讨 Folly
库中 SmallVector
容器提供的各种 API,旨在为读者提供一个全面、权威的 API 参考指南。我们将详细解析每个 API 的功能、参数、返回值、异常处理以及使用场景,并通过实战代码示例,帮助读者彻底掌握 SmallVector
的使用方法,充分发挥其在性能和效率方面的优势。无论你是初学者还是经验丰富的工程师,本章内容都将成为你深入理解和应用 SmallVector
的宝贵资源。
5.1 构造函数与赋值运算符详解(Constructors and Assignment Operators Detailed Analysis)
SmallVector
提供了多种构造函数和赋值运算符,以支持灵活的对象创建和赋值操作。本节将逐一深入解析这些构造函数和赋值运算符,帮助读者理解它们的功能、适用场景以及使用注意事项。
5.1.1 默认构造函数(Default Constructor)
函数签名(Function Signature):
1
SmallVector() noexcept;
描述(Description):
默认构造函数创建一个空的 SmallVector
对象,其大小(size)为 0。由于 SmallVector
的特性,它会在栈上预留一定的空间(由模板参数 N
决定),但此时并不分配任何元素。
参数(Parameters):
无(None)
返回值(Return Value):
无(None)
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv; // 创建一个可以容纳 4 个 int 的 SmallVector
6
std::cout << "Initial size: " << sv.size() << std::endl; // 输出初始大小
7
std::cout << "Initial capacity: " << sv.capacity() << std::endl; // 输出初始容量
8
return 0;
9
}
输出:
1
Initial size: 0
2
Initial capacity: 4
要点(Key Points):
① 默认构造函数是最简单的构造方式,适用于在后续代码中动态添加元素的情况。
② 初始容量由模板参数 N
决定,但不占用实际存储空间,只有在添加元素时才会使用。
5.1.2 容量构造函数(Capacity Constructor)
函数签名(Function Signature):
1
explicit SmallVector(size_type capacity);
描述(Description):
容量构造函数创建一个空的 SmallVector
对象,并预先分配至少能容纳 capacity
个元素的空间。这可以避免在后续添加元素时频繁的内存重新分配,提高性能。
参数(Parameters):
⚝ capacity
(size_type
): 期望的初始容量大小。
返回值(Return Value):
无(None)
异常(Exceptions):
⚝ std::bad_alloc
: 如果内存分配失败,可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv(10); // 创建一个初始容量为 10 的 SmallVector
6
std::cout << "Initial size: " << sv.size() << std::endl; // 输出初始大小
7
std::cout << "Initial capacity: " << sv.capacity() << std::endl; // 输出初始容量
8
return 0;
9
}
输出 (输出可能因实现而异,但容量至少为 10):
1
Initial size: 0
2
Initial capacity: 10
要点(Key Points):
① explicit
关键字防止隐式类型转换,必须显式调用构造函数。
② 预先指定容量适用于已知大致元素数量的场景,可以提升性能。
③ 实际分配的容量可能大于等于 capacity
,具体取决于实现。
5.1.3 范围构造函数(Range Constructor)
函数签名(Function Signature):
1
template <class InputIterator>
2
SmallVector(InputIterator first, InputIterator last);
描述(Description):
范围构造函数使用迭代器 [first, last)
指定的范围内的元素来初始化 SmallVector
。它会复制范围内的所有元素到新的 SmallVector
对象中。
参数(Parameters):
⚝ first
(InputIterator
): 输入范围的起始迭代器。
⚝ last
(InputIterator
): 输入范围的结束迭代器。
返回值(Return Value):
无(None)
异常(Exceptions):
⚝ 可能抛出输入迭代器操作或元素复制过程中抛出的任何异常。
⚝ std::bad_alloc
: 如果内存分配失败,可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <vector>
3
#include <iostream>
4
5
int main() {
6
std::vector<int> vec = {1, 2, 3, 4, 5};
7
folly::SmallVector<int, 4> sv(vec.begin(), vec.end()); // 使用 vector 初始化 SmallVector
8
9
std::cout << "Size: " << sv.size() << std::endl; // 输出大小
10
std::cout << "Capacity: " << sv.capacity() << std::endl; // 输出容量
11
for (int val : sv) {
12
std::cout << val << " "; // 输出 SmallVector 中的元素
13
}
14
std::cout << std::endl;
15
return 0;
16
}
输出 (输出可能因实现而异,但容量至少为 5):
1
Size: 5
2
Capacity: 5
3
1 2 3 4 5
要点(Key Points):
① 适用于从其他容器或数据范围初始化 SmallVector
的场景。
② 会复制范围内的元素,如果元素类型是自定义类,会调用其拷贝构造函数。
③ 迭代器类型只需要满足输入迭代器的要求。
5.1.4 初始化列表构造函数(Initializer List Constructor)
函数签名(Function Signature):
1
SmallVector(std::initializer_list<value_type> il);
描述(Description):
初始化列表构造函数使用 std::initializer_list
提供的元素列表来初始化 SmallVector
。这是一种简洁方便的初始化方式,尤其适用于在代码中直接指定元素值。
参数(Parameters):
⚝ il
(std::initializer_list<value_type>
): 初始化列表。
返回值(Return Value):
无(None)
异常(Exceptions):
⚝ 可能抛出元素复制过程中抛出的任何异常。
⚝ std::bad_alloc
: 如果内存分配失败,可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {1, 2, 3, 4, 5}; // 使用初始化列表初始化 SmallVector
6
7
std::cout << "Size: " << sv.size() << std::endl; // 输出大小
8
std::cout << "Capacity: " << sv.capacity() << std::endl; // 输出容量
9
for (int val : sv) {
10
std::cout << val << " "; // 输出 SmallVector 中的元素
11
}
12
std::cout << std::endl;
13
return 0;
14
}
输出 (输出可能因实现而异,但容量至少为 5):
1
Size: 5
2
Capacity: 5
3
1 2 3 4 5
要点(Key Points):
① C++11 引入的特性,语法简洁,易于使用。
② 适用于在编译时已知元素值的场景。
③ 同样会复制列表中的元素。
5.1.5 拷贝构造函数(Copy Constructor)
函数签名(Function Signature):
1
SmallVector(const SmallVector& other);
描述(Description):
拷贝构造函数创建一个新的 SmallVector
对象,它是 other
对象的深拷贝。这意味着新对象拥有 other
对象所有元素的副本,并且拥有独立的内存空间。
参数(Parameters):
⚝ other
(const SmallVector&
): 要拷贝的 SmallVector
对象。
返回值(Return Value):
无(None)
异常(Exceptions):
⚝ 可能抛出元素拷贝过程中抛出的任何异常。
⚝ std::bad_alloc
: 如果内存分配失败,可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv1 = {1, 2, 3};
6
folly::SmallVector<int, 4> sv2 = sv1; // 使用拷贝构造函数创建 sv2
7
8
sv1[0] = 100; // 修改 sv1 的元素,sv2 不受影响
9
10
std::cout << "sv1: ";
11
for (int val : sv1) {
12
std::cout << val << " ";
13
}
14
std::cout << std::endl;
15
16
std::cout << "sv2: ";
17
for (int val : sv2) {
18
std::cout << val << " ";
19
}
20
std::cout << std::endl;
21
return 0;
22
}
输出:
1
sv1: 100 2 3
2
sv2: 1 2 3
要点(Key Points):
① 执行深拷贝,修改拷贝源对象不会影响拷贝目标对象。
② 如果元素类型是自定义类,会调用其拷贝构造函数。
③ 拷贝构造函数在对象按值传递或作为函数返回值时会被隐式调用。
5.1.6 移动构造函数(Move Constructor)
函数签名(Function Signature):
1
SmallVector(SmallVector&& other) noexcept;
描述(Description):
移动构造函数创建一个新的 SmallVector
对象,并从 other
对象“移动”资源,而不是拷贝。这意味着新对象会接管 other
对象的内存空间和元素,而 other
对象将处于有效的、但未指定的状态。移动构造函数通常比拷贝构造函数更高效,尤其是在处理大型对象时。
参数(Parameters):
⚝ other
(SmallVector&&
): 要移动的 SmallVector
对象(右值引用)。
返回值(Return Value):
无(None)
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
folly::SmallVector<int, 4> createSmallVector() {
5
folly::SmallVector<int, 4> sv = {1, 2, 3};
6
return sv; // 返回右值,触发移动构造
7
}
8
9
int main() {
10
folly::SmallVector<int, 4> sv2 = createSmallVector(); // 使用移动构造函数创建 sv2
11
12
std::cout << "sv2: ";
13
for (int val : sv2) {
14
std::cout << val << " ";
15
}
16
std::cout << std::endl;
17
18
// sv1 的状态变为有效但未指定,不应再访问其元素 (此处为了演示,实际使用中应避免)
19
// std::cout << "sv1: " << sv1.size() << std::endl; // 可能会崩溃或输出不确定值
20
21
return 0;
22
}
输出:
1
sv2: 1 2 3
要点(Key Points):
① 执行浅拷贝,资源所有权转移,避免了深拷贝的开销。
② 移动构造函数通常在右值引用或 std::move
的情况下被调用。
③ 移动源对象 other
在移动后处于有效但未指定的状态,不应再依赖其内部数据。
5.1.7 拷贝赋值运算符(Copy Assignment Operator)
函数签名(Function Signature):
1
SmallVector& operator=(const SmallVector& other);
描述(Description):
拷贝赋值运算符将 other
对象的内容拷贝赋值给当前对象。如果当前对象已经分配了内存,并且容量不足以容纳 other
对象的所有元素,则可能需要重新分配内存。
参数(Parameters):
⚝ other
(const SmallVector&
): 要拷贝赋值的 SmallVector
对象。
返回值(Return Value):
⚝ SmallVector&
: 对当前对象的引用 (*this
)。
异常(Exceptions):
⚝ 可能抛出元素拷贝过程中抛出的任何异常。
⚝ std::bad_alloc
: 如果内存分配失败,可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv1 = {1, 2};
6
folly::SmallVector<int, 4> sv2 = {3, 4, 5};
7
8
sv1 = sv2; // 使用拷贝赋值运算符将 sv2 赋值给 sv1
9
10
sv2[0] = 300; // 修改 sv2 的元素,sv1 不受影响
11
12
std::cout << "sv1: ";
13
for (int val : sv1) {
14
std::cout << val << " ";
15
}
16
std::cout << std::endl;
17
18
std::cout << "sv2: ";
19
for (int val : sv2) {
20
std::cout << val << " ";
21
}
22
std::cout << std::endl;
23
return 0;
24
}
输出:
1
sv1: 3 4 5
2
sv2: 300 4 5
要点(Key Points):
① 执行深拷贝赋值,修改赋值源对象不会影响赋值目标对象。
② 赋值运算符需要处理自赋值的情况 (sv1 = sv1
),但 SmallVector
通常能正确处理。
③ 返回对自身对象的引用,支持链式赋值操作 (sv1 = sv2 = sv3
)。
5.1.8 移动赋值运算符(Move Assignment Operator)
函数签名(Function Signature):
1
SmallVector& operator=(SmallVector&& other) noexcept;
描述(Description):
移动赋值运算符将 other
对象的内容移动赋值给当前对象。与移动构造函数类似,它会转移资源所有权,避免不必要的拷贝操作。
参数(Parameters):
⚝ other
(SmallVector&&
): 要移动赋值的 SmallVector
对象(右值引用)。
返回值(Return Value):
⚝ SmallVector&
: 对当前对象的引用 (*this
)。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv1 = {1, 2};
6
folly::SmallVector<int, 4> sv2 = {3, 4, 5};
7
8
sv1 = std::move(sv2); // 使用移动赋值运算符将 sv2 移动赋值给 sv1
9
10
std::cout << "sv1: ";
11
for (int val : sv1) {
12
std::cout << val << " ";
13
}
14
std::cout << std::endl;
15
16
// sv2 的状态变为有效但未指定,不应再访问其元素 (此处为了演示,实际使用中应避免)
17
// std::cout << "sv2: " << sv2.size() << std::endl; // 可能会崩溃或输出不确定值
18
return 0;
19
}
输出:
1
sv1: 3 4 5
要点(Key Points):
① 执行浅拷贝赋值,资源所有权转移,提升性能。
② 移动赋值运算符通常在右值引用或 std::move
的情况下被调用。
③ 移动源对象 other
在移动后处于有效但未指定的状态,不应再依赖其内部数据。
④ 返回对自身对象的引用,支持链式赋值操作。
5.2 容量函数详解(Capacity Functions Detailed Analysis)
SmallVector
提供了一系列容量函数,用于查询和管理容器的内存容量和大小。合理使用这些函数可以帮助我们更好地理解和控制 SmallVector
的内存使用,优化程序性能。
5.2.1 size()
函数签名(Function Signature):
1
size_type size() const noexcept;
描述(Description):
size()
函数返回 SmallVector
中当前元素的数量,即容器的大小。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ size_type
: 容器中元素的数量。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {1, 2, 3};
6
std::cout << "Size: " << sv.size() << std::endl; // 输出元素数量
7
return 0;
8
}
输出:
1
Size: 3
要点(Key Points):
① size()
返回的是逻辑大小,即用户可见的元素数量。
② 时间复杂度为 \(O(1)\)。
5.2.2 capacity()
函数签名(Function Signature):
1
size_type capacity() const noexcept;
描述(Description):
capacity()
函数返回 SmallVector
当前已分配的存储空间可以容纳的元素数量,即容器的容量。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ size_type
: 容器的容量。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {1, 2, 3};
6
std::cout << "Capacity: " << sv.capacity() << std::endl; // 输出容器容量
7
return 0;
8
}
输出 (输出可能因实现而异,但容量至少为 3):
1
Capacity: 4
要点(Key Points):
① capacity()
返回的是物理容量,表示容器在不重新分配内存的情况下可以容纳的最大元素数量。
② capacity()
总是大于等于 size()
。
③ 时间复杂度为 \(O(1)\)。
5.2.3 max_size()
函数签名(Function Signature):
1
size_type max_size() const noexcept;
描述(Description):
max_size()
函数返回 SmallVector
理论上可以容纳的最大元素数量,这通常受限于系统内存和实现限制。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ size_type
: 容器的最大容量。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv;
6
std::cout << "Max size: " << sv.max_size() << std::endl; // 输出最大容量
7
return 0;
8
}
输出 (输出值会非常大):
1
Max size: 4611686018427387903
要点(Key Points):
① max_size()
返回的是理论上的最大值,实际应用中不太可能达到。
② 主要用于了解容器容量的上限。
③ 时间复杂度为 \(O(1)\)。
5.2.4 empty()
函数签名(Function Signature):
1
bool empty() const noexcept;
描述(Description):
empty()
函数检查 SmallVector
是否为空,即容器中是否没有任何元素。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ bool
: 如果容器为空,返回 true
;否则返回 false
。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv1;
6
folly::SmallVector<int, 4> sv2 = {1, 2, 3};
7
8
std::cout << "sv1 is empty: " << sv1.empty() << std::endl; // 检查 sv1 是否为空
9
std::cout << "sv2 is empty: " << sv2.empty() << std::endl; // 检查 sv2 是否为空
10
return 0;
11
}
输出:
1
sv1 is empty: true
2
sv2 is empty: false
要点(Key Points):
① 判断容器是否为空的便捷方法,等价于 size() == 0
。
② 时间复杂度为 \(O(1)\)。
5.2.5 reserve()
函数签名(Function Signature):
1
void reserve(size_type n);
描述(Description):
reserve()
函数请求 SmallVector
预留至少能容纳 n
个元素的存储空间。如果在调用 reserve()
之前,容器的 capacity()
小于 n
,则会重新分配内存,使其容量至少达到 n
。如果 n
小于或等于当前的 capacity()
,则 reserve()
不会产生任何效果。
参数(Parameters):
⚝ n
(size_type
): 期望的最小容量。
返回值(Return Value):
无(None)
异常(Exceptions):
⚝ std::length_error
: 如果 n
大于 max_size()
,则抛出此异常。
⚝ std::bad_alloc
: 如果内存分配失败,可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv;
6
std::cout << "Initial capacity: " << sv.capacity() << std::endl; // 输出初始容量
7
8
sv.reserve(10); // 预留容量为 10
9
std::cout << "Capacity after reserve(10): " << sv.capacity() << std::endl; // 输出预留后的容量
10
11
sv.reserve(5); // 预留容量为 5 (小于当前容量,无效果)
12
std::cout << "Capacity after reserve(5): " << sv.capacity() << std::endl; // 输出预留后的容量
13
return 0;
14
}
输出 (输出可能因实现而异,但容量变化符合预期):
1
Initial capacity: 4
2
Capacity after reserve(10): 10
3
Capacity after reserve(5): 10
要点(Key Points):
① 用于预先分配内存,避免在后续添加元素时频繁的内存重新分配,提高性能。
② reserve()
只会增加容量,不会改变 size()
。
③ 如果预留的容量小于当前容量,不会缩小容量。
5.2.6 shrink_to_fit()
函数签名(Function Signature):
1
void shrink_to_fit();
描述(Description):
shrink_to_fit()
函数尝试释放 SmallVector
中未使用的内存空间,将容器的 capacity()
减小到与 size()
相匹配的大小,或者一个更小的值,以节省内存。但请注意,具体的实现可能不会真正缩小容量,这取决于分配器的策略。
参数(Parameters):
无(None)
返回值(Return Value):
无(None)
异常(Exceptions):
⚝ std::bad_alloc
: 如果内存分配失败(在某些实现中,缩小容量可能涉及重新分配内存),可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv;
6
sv.reserve(100); // 预留大容量
7
sv.push_back(1);
8
sv.push_back(2);
9
std::cout << "Capacity before shrink_to_fit: " << sv.capacity() << std::endl; // 输出 shrink_to_fit 前的容量
10
11
sv.shrink_to_fit(); // 尝试缩小容量
12
std::cout << "Capacity after shrink_to_fit: " << sv.capacity() << std::endl; // 输出 shrink_to_fit 后的容量
13
return 0;
14
}
输出 (输出可能因实现而异,但容量应该减小):
1
Capacity before shrink_to_fit: 100
2
Capacity after shrink_to_fit: 2
要点(Key Points):
① 用于释放不再需要的内存空间,减少内存占用。
② shrink_to_fit()
只是一个请求,具体的缩小行为取决于实现和分配器。
③ 调用 shrink_to_fit()
后,容器的迭代器、指针和引用仍然有效。
5.3 元素访问函数详解(Element Access Functions Detailed Analysis)
SmallVector
提供多种方式来访问容器中的元素,包括索引访问、范围访问以及首尾元素访问等。这些函数使得我们可以方便快捷地读取和修改容器中的元素。
5.3.1 operator[]
函数签名(Function Signature):
1
reference operator[](size_type pos);
2
const_reference operator[](size_type pos) const;
描述(Description):
operator[]
提供对 SmallVector
中指定位置 pos
元素的直接访问。它不进行边界检查,因此如果 pos
超出有效范围 [0, size() - 1]
,则行为未定义(Undefined Behavior),可能导致程序崩溃或数据损坏。
参数(Parameters):
⚝ pos
(size_type
): 要访问元素的索引位置,范围为 [0, size() - 1]
。
返回值(Return Value):
⚝ reference
: 对指定位置元素的引用(非 const
版本)。
⚝ const_reference
: 对指定位置元素的常量引用(const
版本)。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
std::cout << "Element at index 1: " << sv[1] << std::endl; // 访问索引为 1 的元素
8
9
sv[0] = 100; // 修改索引为 0 的元素
10
std::cout << "Element at index 0 after modification: " << sv[0] << std::endl;
11
12
// 注意:以下代码是错误的,因为索引 3 超出范围,会导致未定义行为
13
// std::cout << sv[3] << std::endl; // 错误用法,可能崩溃
14
return 0;
15
}
输出:
1
Element at index 1: 20
2
Element at index 0 after modification: 100
要点(Key Points):
① 提供快速的元素访问方式,时间复杂度为 \(O(1)\)。
② 不进行边界检查,使用时务必确保索引有效,避免未定义行为。
③ const
版本用于只读访问,非 const
版本用于读写访问。
5.3.2 at()
函数签名(Function Signature):
1
reference at(size_type pos);
2
const_reference at(size_type pos) const;
描述(Description):
at()
函数也提供对 SmallVector
中指定位置 pos
元素的访问,但与 operator[]
不同的是,at()
会进行边界检查。如果 pos
超出有效范围 [0, size() - 1]
,则会抛出 std::out_of_range
异常。
参数(Parameters):
⚝ pos
(size_type
): 要访问元素的索引位置,范围为 [0, size() - 1]
。
返回值(Return Value):
⚝ reference
: 对指定位置元素的引用(非 const
版本)。
⚝ const_reference
: 对指定位置元素的常量引用(const
版本)。
异常(Exceptions):
⚝ std::out_of_range
: 如果 pos
超出有效范围,则抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
#include <stdexcept>
4
5
int main() {
6
folly::SmallVector<int, 4> sv = {10, 20, 30};
7
8
std::cout << "Element at index 1: " << sv.at(1) << std::endl; // 访问索引为 1 的元素
9
10
try {
11
std::cout << sv.at(3) << std::endl; // 访问超出范围的索引,会抛出异常
12
} catch (const std::out_of_range& e) {
13
std::cerr << "Out of range exception caught: " << e.what() << std::endl;
14
}
15
return 0;
16
}
输出:
1
Element at index 1: 20
2
Out of range exception caught: folly::SmallVector::at: index out of range
要点(Key Points):
① 提供安全的元素访问方式,时间复杂度为 \(O(1)\)。
② 进行边界检查,超出范围会抛出异常,有助于提高程序的健壮性。
③ const
版本用于只读访问,非 const
版本用于读写访问。
④ 在需要确保索引有效性的场景下,推荐使用 at()
。
5.3.3 front()
函数签名(Function Signature):
1
reference front();
2
const_reference front() const;
描述(Description):
front()
函数返回 SmallVector
中第一个元素的引用。调用 front()
之前,必须确保容器非空,否则行为未定义。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ reference
: 对第一个元素的引用(非 const
版本)。
⚝ const_reference
: 对第一个元素的常量引用(const
版本)。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
但如果容器为空,则行为未定义。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
if (!sv.empty()) {
8
std::cout << "First element: " << sv.front() << std::endl; // 访问第一个元素
9
sv.front() = 100; // 修改第一个元素
10
std::cout << "First element after modification: " << sv.front() << std::endl;
11
} else {
12
std::cout << "SmallVector is empty." << std::endl;
13
}
14
return 0;
15
}
输出:
1
First element: 10
2
First element after modification: 100
要点(Key Points):
① 快速访问第一个元素,时间复杂度为 \(O(1)\)。
② 调用前必须确保容器非空,否则可能导致未定义行为。
③ const
版本用于只读访问,非 const
版本用于读写访问。
5.3.4 back()
函数签名(Function Signature):
1
reference back();
2
const_reference back() const;
描述(Description):
back()
函数返回 SmallVector
中最后一个元素的引用。调用 back()
之前,必须确保容器非空,否则行为未定义。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ reference
: 对最后一个元素的引用(非 const
版本)。
⚝ const_reference
: 对最后一个元素的常量引用(const
版本)。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
但如果容器为空,则行为未定义。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
if (!sv.empty()) {
8
std::cout << "Last element: " << sv.back() << std::endl; // 访问最后一个元素
9
sv.back() = 300; // 修改最后一个元素
10
std::cout << "Last element after modification: " << sv.back() << std::endl;
11
} else {
12
std::cout << "SmallVector is empty." << std::endl;
13
}
14
return 0;
15
}
输出:
1
Last element: 30
2
Last element after modification: 300
要点(Key Points):
① 快速访问最后一个元素,时间复杂度为 \(O(1)\)。
② 调用前必须确保容器非空,否则可能导致未定义行为。
③ const
版本用于只读访问,非 const
版本用于读写访问。
5.3.5 data()
函数签名(Function Signature):
1
pointer data() noexcept;
2
const_pointer data() const noexcept;
描述(Description):
data()
函数返回指向 SmallVector
内部存储数组首元素的指针。如果 SmallVector
为空,则返回值未指定,但可以解引用(C++11 标准之后)。通常返回 nullptr
当容器为空。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ pointer
: 指向首元素的指针(非 const
版本)。
⚝ const_pointer
: 指向首元素的常量指针(const
版本)。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
int* ptr = sv.data(); // 获取指向首元素的指针
8
std::cout << "First element using data(): " << *ptr << std::endl; // 通过指针访问首元素
9
10
for (size_t i = 0; i < sv.size(); ++i) {
11
std::cout << *(ptr + i) << " "; // 使用指针遍历元素
12
}
13
std::cout << std::endl;
14
return 0;
15
}
输出:
1
First element using data(): 10
2
10 20 30
要点(Key Points):
① 提供对底层数组的直接访问,可以用于与 C 风格 API 交互或进行底层操作。
② 返回的指针类型与元素类型匹配。
③ 如果容器为空,返回的指针可能为 nullptr
或其他未指定值,具体取决于实现。 建议在使用前检查容器是否为空。
④ 通过 data()
获取的指针,可以像操作普通数组一样操作 SmallVector
的元素。
5.4 修改器函数详解(Modifier Functions Detailed Analysis)
SmallVector
提供了一系列修改器函数,用于添加、删除、插入、清除和调整容器中的元素。这些函数使得我们可以灵活地操作 SmallVector
的内容。
5.4.1 push_back()
函数签名(Function Signature):
1
void push_back(const value_type& value);
2
void push_back(value_type&& value);
描述(Description):
push_back()
函数在 SmallVector
的末尾添加一个新元素。它有两个重载版本:
⚝ 接受左值引用 (const value_type&
) 的版本会拷贝元素。
⚝ 接受右值引用 (value_type&&
) 的版本会移动元素(如果元素类型支持移动语义)。
参数(Parameters):
⚝ value
(const value_type&
或 value_type&&
): 要添加到末尾的元素。
返回值(Return Value):
无(None)
异常(Exceptions):
⚝ 可能抛出元素拷贝或移动过程中抛出的任何异常。
⚝ std::bad_alloc
: 如果需要重新分配内存且分配失败,可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv;
6
7
sv.push_back(10); // 添加元素 10
8
sv.push_back(20); // 添加元素 20
9
sv.push_back(30); // 添加元素 30
10
11
std::cout << "SmallVector after push_back: ";
12
for (int val : sv) {
13
std::cout << val << " ";
14
}
15
std::cout << std::endl;
16
std::cout << "Size: " << sv.size() << std::endl;
17
std::cout << "Capacity: " << sv.capacity() << std::endl;
18
return 0;
19
}
输出 (输出可能因实现而异,但容量变化符合预期):
1
SmallVector after push_back: 10 20 30
2
Size: 3
3
Capacity: 4
要点(Key Points):
① 在末尾添加元素的常用方法,时间复杂度平均为 \(O(1)\),最坏情况(需要重新分配内存)为 \(O(n)\)。
② 优先使用移动版本,避免不必要的拷贝操作,尤其是在添加临时对象或右值时。
③ 如果添加元素后超出当前容量,SmallVector
会自动重新分配内存,通常以指数方式增长容量。
5.4.2 emplace_back()
函数签名(Function Signature):
1
template <class... Args>
2
reference emplace_back(Args&&... args);
描述(Description):
emplace_back()
函数在 SmallVector
的末尾就地构造一个新元素。它接受构造元素的参数,并在容器内部直接使用这些参数构造元素,避免了临时对象的创建和拷贝或移动操作。
参数(Parameters):
⚝ args...
(Args&&...
): 用于构造新元素的参数,参数类型和数量取决于元素类型的构造函数。
返回值(Return Value):
⚝ reference
: 对新添加元素的引用。
异常(Exceptions):
⚝ 可能抛出元素构造过程中抛出的任何异常。
⚝ std::bad_alloc
: 如果需要重新分配内存且分配失败,可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
struct MyStruct {
5
int value;
6
MyStruct(int v) : value(v) {
7
std::cout << "MyStruct constructed with value: " << value << std::endl;
8
}
9
MyStruct(const MyStruct& other) : value(other.value) {
10
std::cout << "MyStruct copy constructed with value: " << value << std::endl;
11
}
12
MyStruct(MyStruct&& other) noexcept : value(other.value) {
13
std::cout << "MyStruct move constructed with value: " << value << std::endl;
14
}
15
};
16
17
int main() {
18
folly::SmallVector<MyStruct, 4> sv;
19
20
std::cout << "Using emplace_back:" << std::endl;
21
sv.emplace_back(10); // 就地构造 MyStruct 对象,避免拷贝或移动
22
23
std::cout << "Using push_back:" << std::endl;
24
MyStruct temp(20);
25
sv.push_back(temp); // 先构造临时对象,再拷贝到 SmallVector 中
26
27
std::cout << "SmallVector size: " << sv.size() << std::endl;
28
return 0;
29
}
输出 (输出可能因编译器优化而略有不同,但 emplace_back 应该避免拷贝构造):
1
Using emplace_back:
2
MyStruct constructed with value: 10
3
Using push_back:
4
MyStruct constructed with value: 20
5
MyStruct copy constructed with value: 20
6
SmallVector size: 2
要点(Key Points):
① 高效的元素添加方法,尤其适用于元素类型构造复杂或开销较大的情况。
② 避免了临时对象的创建和拷贝或移动,直接在容器内部构造元素。
③ 时间复杂度与 push_back()
相同,平均为 \(O(1)\),最坏情况为 \(O(n)\)。
④ emplace_back()
是 C++11 引入的特性。
5.4.3 pop_back()
函数签名(Function Signature):
1
void pop_back();
描述(Description):
pop_back()
函数移除 SmallVector
的最后一个元素。调用 pop_back()
之前,必须确保容器非空,否则行为未定义。
参数(Parameters):
无(None)
返回值(Return Value):
无(None)
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
但如果容器为空,则行为未定义。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
if (!sv.empty()) {
8
sv.pop_back(); // 移除最后一个元素
9
std::cout << "SmallVector after pop_back: ";
10
for (int val : sv) {
11
std::cout << val << " ";
12
}
13
std::cout << std::endl;
14
std::cout << "Size: " << sv.size() << std::endl;
15
} else {
16
std::cout << "SmallVector is empty." << std::endl;
17
}
18
return 0;
19
}
输出:
1
SmallVector after pop_back: 10 20
2
Size: 2
要点(Key Points):
① 移除末尾元素的常用方法,时间复杂度为 \(O(1)\)。
② 调用前必须确保容器非空,否则可能导致未定义行为。
③ pop_back()
只会减少 size()
,不会改变 capacity()
。
5.4.4 insert()
函数签名(Function Signature):
1
iterator insert(const_iterator pos, const value_type& value);
2
iterator insert(const_iterator pos, value_type&& value);
3
iterator insert(const_iterator pos, size_type count, const value_type& value);
4
template <class InputIterator>
5
iterator insert(const_iterator pos, InputIterator first, InputIterator last);
6
iterator insert(const_iterator pos, std::initializer_list<value_type> il);
描述(Description):
insert()
函数在 SmallVector
的指定位置 pos
插入一个或多个新元素。它提供了多个重载版本,支持插入单个元素、多个相同元素、范围内的元素以及初始化列表中的元素。
参数(Parameters):
⚝ pos
(const_iterator
): 插入位置的迭代器。
⚝ value
(const value_type&
或 value_type&&
): 要插入的元素(单个元素插入版本)。
⚝ count
(size_type
): 要插入的元素数量(多个相同元素插入版本)。
⚝ first
(InputIterator
): 输入范围的起始迭代器(范围插入版本)。
⚝ last
(InputIterator
): 输入范围的结束迭代器(范围插入版本)。
⚝ il
(std::initializer_list<value_type>
): 初始化列表(初始化列表插入版本)。
返回值(Return Value):
⚝ iterator
: 指向新插入的第一个元素的迭代器,或者如果 count
为 0,则返回 pos
。
异常(Exceptions):
⚝ 可能抛出元素拷贝或移动过程中抛出的任何异常。
⚝ std::bad_alloc
: 如果需要重新分配内存且分配失败,可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <vector>
3
#include <iostream>
4
5
int main() {
6
folly::SmallVector<int, 4> sv = {10, 20, 30};
7
8
// 插入单个元素
9
auto it1 = sv.insert(sv.begin() + 1, 15); // 在索引 1 处插入 15
10
std::cout << "SmallVector after single insert: ";
11
for (int val : sv) {
12
std::cout << val << " ";
13
}
14
std::cout << std::endl;
15
std::cout << "Iterator to inserted element: " << *it1 << std::endl;
16
17
// 插入多个相同元素
18
sv.insert(sv.end(), 2, 50); // 在末尾插入 2 个 50
19
std::cout << "SmallVector after multiple insert: ";
20
for (int val : sv) {
21
std::cout << val << " ";
22
}
23
std::cout << std::endl;
24
25
// 插入范围元素
26
std::vector<int> vec = {60, 70};
27
sv.insert(sv.begin(), vec.begin(), vec.end()); // 在开头插入 vector 的元素
28
std::cout << "SmallVector after range insert: ";
29
for (int val : sv) {
30
std::cout << val << " ";
31
}
32
std::cout << std::endl;
33
34
// 插入初始化列表元素
35
sv.insert(sv.begin() + 3, {80, 90}); // 在索引 3 处插入初始化列表的元素
36
std::cout << "SmallVector after initializer list insert: ";
37
for (int val : sv) {
38
std::cout << val << " ";
39
}
40
std::cout << std::endl;
41
42
return 0;
43
}
输出 (输出可能因实现而异,但元素插入位置和顺序符合预期):
1
SmallVector after single insert: 10 15 20 30
2
Iterator to inserted element: 15
3
SmallVector after multiple insert: 10 15 20 30 50 50
4
SmallVector after range insert: 60 70 10 15 20 30 50 50
5
SmallVector after initializer list insert: 60 70 10 80 90 15 20 30 50 50
要点(Key Points):
① 在指定位置插入元素,时间复杂度平均为 \(O(n)\)(因为需要移动插入位置之后的元素)。
② 插入位置由迭代器指定,需要使用有效的迭代器。
③ 多个重载版本提供了灵活的插入方式。
④ 插入元素后,迭代器可能会失效,需要重新获取。
5.4.5 emplace()
函数签名(Function Signature):
1
template <class... Args>
2
iterator emplace(const_iterator pos, Args&&... args);
描述(Description):
emplace()
函数在 SmallVector
的指定位置 pos
就地构造一个新元素。与 emplace_back()
类似,它接受构造元素的参数,并在容器内部直接使用这些参数构造元素,避免了临时对象的创建和拷贝或移动操作。
参数(Parameters):
⚝ pos
(const_iterator
): 插入位置的迭代器。
⚝ args...
(Args&&...
): 用于构造新元素的参数,参数类型和数量取决于元素类型的构造函数。
返回值(Return Value):
⚝ iterator
: 指向新插入元素的迭代器。
异常(Exceptions):
⚝ 可能抛出元素构造过程中抛出的任何异常。
⚝ std::bad_alloc
: 如果需要重新分配内存且分配失败,可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
struct MyStruct {
5
int value;
6
MyStruct(int v) : value(v) {
7
std::cout << "MyStruct constructed with value: " << value << std::endl;
8
}
9
MyStruct(const MyStruct& other) : value(other.value) {
10
std::cout << "MyStruct copy constructed with value: " << value << std::endl;
11
}
12
MyStruct(MyStruct&& other) noexcept : value(other.value) {
13
std::cout << "MyStruct move constructed with value: " << value << std::endl;
14
}
15
};
16
17
int main() {
18
folly::SmallVector<MyStruct, 4> sv = {MyStruct(10), MyStruct(20)};
19
20
std::cout << "Using emplace:" << std::endl;
21
sv.emplace(sv.begin() + 1, 15); // 在索引 1 处就地构造 MyStruct 对象
22
23
std::cout << "SmallVector size: " << sv.size() << std::endl;
24
return 0;
25
}
输出 (输出可能因编译器优化而略有不同,但 emplace 应该避免拷贝构造):
1
MyStruct constructed with value: 10
2
MyStruct constructed with value: 20
3
Using emplace:
4
MyStruct constructed with value: 15
5
SmallVector size: 3
要点(Key Points):
① 高效的元素插入方法,尤其适用于元素类型构造复杂或开销较大的情况。
② 避免了临时对象的创建和拷贝或移动,直接在容器内部构造元素。
③ 时间复杂度与 insert()
相同,平均为 \(O(n)\)。
④ emplace()
是 C++11 引入的特性。
5.4.6 erase()
函数签名(Function Signature):
1
iterator erase(const_iterator pos);
2
iterator erase(const_iterator first, const_iterator last);
描述(Description):
erase()
函数从 SmallVector
中移除指定位置或范围内的元素。它有两个重载版本:
⚝ 移除单个元素(通过迭代器 pos
指定位置)。
⚝ 移除范围内的元素(通过迭代器 [first, last)
指定范围)。
参数(Parameters):
⚝ pos
(const_iterator
): 要移除元素的迭代器(单个元素移除版本)。
⚝ first
(const_iterator
): 要移除范围的起始迭代器(范围移除版本)。
⚝ last
(const_iterator
): 要移除范围的结束迭代器(范围移除版本)。
返回值(Return Value):
⚝ iterator
: 指向被移除元素之后元素的迭代器,或者如果移除的是最后一个元素,则返回 end()
。
异常(Exceptions):
⚝ 可能抛出元素析构过程中抛出的任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30, 40, 50};
6
7
// 移除单个元素
8
auto it1 = sv.erase(sv.begin() + 2); // 移除索引 2 的元素 (30)
9
std::cout << "SmallVector after single erase: ";
10
for (int val : sv) {
11
std::cout << val << " ";
12
}
13
std::cout << std::endl;
14
std::cout << "Iterator after erase: " << *it1 << std::endl; // 指向 40
15
16
// 移除范围元素
17
sv.erase(sv.begin(), sv.begin() + 2); // 移除前两个元素 (10, 20)
18
std::cout << "SmallVector after range erase: ";
19
for (int val : sv) {
20
std::cout << val << " ";
21
}
22
std::cout << std::endl;
23
24
return 0;
25
}
输出 (输出可能因实现而异,但元素移除位置和顺序符合预期):
1
SmallVector after single erase: 10 20 40 50
2
Iterator after erase: 40
3
SmallVector after range erase: 40 50
要点(Key Points):
① 移除指定位置或范围内的元素,时间复杂度平均为 \(O(n)\)(因为需要移动被移除元素之后的元素)。
② 移除位置或范围由迭代器指定,需要使用有效的迭代器。
③ 移除元素后,迭代器可能会失效,需要重新获取。
④ 返回的迭代器指向被移除元素之后的位置,方便链式操作。
5.4.7 clear()
函数签名(Function Signature):
1
void clear() noexcept;
描述(Description):
clear()
函数移除 SmallVector
中的所有元素,使其变为空容器。容器的 size()
变为 0,但 capacity()
通常保持不变。
参数(Parameters):
无(None)
返回值(Return Value):
无(None)
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
sv.clear(); // 清空 SmallVector
8
std::cout << "SmallVector after clear: ";
9
for (int val : sv) {
10
std::cout << val << " ";
11
}
12
std::cout << std::endl;
13
std::cout << "Size: " << sv.size() << std::endl;
14
std::cout << "Capacity: " << sv.capacity() << std::endl; // 容量通常不变
15
return 0;
16
}
输出 (输出可能因实现而异,但容量应该保持不变):
1
SmallVector after clear:
2
Size: 0
3
Capacity: 4
要点(Key Points):
① 快速清空容器,时间复杂度为 \(O(n)\)(需要析构所有元素)。
② clear()
只会移除元素,不会释放已分配的内存空间,capacity()
通常保持不变。
③ 如果需要释放内存空间,可以考虑使用 shrink_to_fit()
。
5.4.8 assign()
函数签名(Function Signature):
1
void assign(size_type count, const value_type& value);
2
template <class InputIterator>
3
void assign(InputIterator first, InputIterator last);
4
void assign(std::initializer_list<value_type> il);
描述(Description):
assign()
函数用新元素替换 SmallVector
的现有内容。它提供了多个重载版本,支持用多个相同元素、范围内的元素以及初始化列表中的元素进行赋值。
参数(Parameters):
⚝ count
(size_type
): 要赋值的元素数量(多个相同元素赋值版本)。
⚝ value
(const value_type&
): 要赋值的元素值(多个相同元素赋值版本)。
⚝ first
(InputIterator
): 输入范围的起始迭代器(范围赋值版本)。
⚝ last
(InputIterator
): 输入范围的结束迭代器(范围赋值版本)。
⚝ il
(std::initializer_list<value_type>
): 初始化列表(初始化列表赋值版本)。
返回值(Return Value):
无(None)
异常(Exceptions):
⚝ 可能抛出元素拷贝过程中抛出的任何异常。
⚝ std::bad_alloc
: 如果需要重新分配内存且分配失败,可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <vector>
3
#include <iostream>
4
5
int main() {
6
folly::SmallVector<int, 4> sv;
7
8
// 使用多个相同元素赋值
9
sv.assign(3, 100); // 赋值 3 个 100
10
std::cout << "SmallVector after multiple assign: ";
11
for (int val : sv) {
12
std::cout << val << " ";
13
}
14
std::cout << std::endl;
15
16
// 使用范围元素赋值
17
std::vector<int> vec = {200, 300, 400, 500};
18
sv.assign(vec.begin(), vec.end()); // 使用 vector 的元素赋值
19
std::cout << "SmallVector after range assign: ";
20
for (int val : sv) {
21
std::cout << val << " ";
22
}
23
std::cout << std::endl;
24
25
// 使用初始化列表赋值
26
sv.assign({600, 700}); // 使用初始化列表赋值
27
std::cout << "SmallVector after initializer list assign: ";
28
for (int val : sv) {
29
std::cout << val << " ";
30
}
31
std::cout << std::endl;
32
33
return 0;
34
}
输出 (输出可能因实现而异,但元素赋值和顺序符合预期):
1
SmallVector after multiple assign: 100 100 100
2
SmallVector after range assign: 200 300 400 500
3
SmallVector after initializer list assign: 600 700
要点(Key Points):
① 用新内容替换容器原有内容,时间复杂度取决于赋值元素的数量和类型。
② 多个重载版本提供了灵活的赋值方式。
③ 赋值后,容器的 size()
和 capacity()
可能会发生变化,以适应新的元素。
5.4.9 resize()
函数签名(Function Signature):
1
void resize(size_type n);
2
void resize(size_type n, const value_type& value);
描述(Description):
resize()
函数改变 SmallVector
的大小(size)为 n
。它有两个重载版本:
⚝ resize(n)
: 如果 n
小于当前 size()
,则移除末尾多余的元素;如果 n
大于当前 size()
,则在末尾添加默认构造的元素。
⚝ resize(n, value)
: 如果 n
小于当前 size()
,则移除末尾多余的元素;如果 n
大于当前 size()
,则在末尾添加 value
的副本。
参数(Parameters):
⚝ n
(size_type
): 新的容器大小。
⚝ value
(const value_type&
): 用于填充新增元素的默认值(可选,仅在第二个重载版本中使用)。
返回值(Return Value):
无(None)
异常(Exceptions):
⚝ 可能抛出元素拷贝或默认构造过程中抛出的任何异常。
⚝ std::bad_alloc
: 如果需要重新分配内存且分配失败,可能会抛出此异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30, 40};
6
7
// 缩小 size
8
sv.resize(2); // 调整 size 为 2,移除末尾元素
9
std::cout << "SmallVector after resize(2): ";
10
for (int val : sv) {
11
std::cout << val << " ";
12
}
13
std::cout << std::endl;
14
std::cout << "Size: " << sv.size() << std::endl;
15
16
// 增大 size,使用默认值填充
17
sv.resize(5); // 调整 size 为 5,末尾添加默认构造的元素 (int 默认值为 0)
18
std::cout << "SmallVector after resize(5): ";
19
for (int val : sv) {
20
std::cout << val << " ";
21
}
22
std::cout << std::endl;
23
std::cout << "Size: " << sv.size() << std::endl;
24
25
// 增大 size,使用指定值填充
26
sv.resize(8, 100); // 调整 size 为 8,末尾添加 100
27
std::cout << "SmallVector after resize(8, 100): ";
28
for (int val : sv) {
29
std::cout << val << " ";
30
}
31
std::cout << std::endl;
32
std::cout << "Size: " << sv.size() << std::endl;
33
34
return 0;
35
}
输出 (输出可能因实现而异,但元素数量和值符合预期):
1
SmallVector after resize(2): 10 20
2
Size: 2
3
SmallVector after resize(5): 10 20 0 0 0
4
Size: 5
5
SmallVector after resize(8, 100): 10 20 0 0 0 100 100 100
6
Size: 8
要点(Key Points):
① 改变容器的大小,可以增大或缩小。
② 增大 size 时,可以指定填充值,也可以使用默认构造值。
③ 缩小 size 时,会移除末尾元素,但 capacity()
通常保持不变。
5.4.10 swap()
函数签名(Function Signature):
1
void swap(SmallVector& other) noexcept;
描述(Description):
swap()
函数交换当前 SmallVector
对象与 other
对象的内容。交换操作通常非常高效,因为它只交换内部指针和容量等信息,而不需要拷贝或移动元素。
参数(Parameters):
⚝ other
(SmallVector&
): 要交换内容的另一个 SmallVector
对象。
返回值(Return Value):
无(None)
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv1 = {10, 20};
6
folly::SmallVector<int, 4> sv2 = {30, 40, 50};
7
8
std::cout << "Before swap:" << std::endl;
9
std::cout << "sv1: "; for (int val : sv1) std::cout << val << " "; std::cout << std::endl;
10
std::cout << "sv2: "; for (int val : sv2) std::cout << val << " "; std::cout << std::endl;
11
12
sv1.swap(sv2); // 交换 sv1 和 sv2 的内容
13
14
std::cout << "After swap:" << std::endl;
15
std::cout << "sv1: "; for (int val : sv1) std::cout << val << " "; std::cout << std::endl;
16
std::cout << "sv2: "; for (int val : sv2) std::cout << val << " "; std::cout << std::endl;
17
18
return 0;
19
}
输出:
1
Before swap:
2
sv1: 10 20
3
sv2: 30 40 50
4
After swap:
5
sv1: 30 40 50
6
sv2: 10 20
要点(Key Points):
① 高效交换两个 SmallVector
对象的内容,时间复杂度为 \(O(1)\)。
② swap()
操作不会使迭代器、指针和引用失效,除非它们指向被交换的容器。
③ 常用于实现移动赋值运算符或优化算法。
5.5 迭代器函数详解(Iterator Functions Detailed Analysis)
SmallVector
提供了多种迭代器函数,用于遍历容器中的元素。迭代器是访问和操作容器元素的通用接口,是 C++ 标准库容器的重要组成部分。
5.5.1 begin()
函数签名(Function Signature):
1
iterator begin() noexcept;
2
const_iterator begin() const noexcept;
描述(Description):
begin()
函数返回指向 SmallVector
中第一个元素的迭代器。如果 SmallVector
为空,则返回的迭代器等于 end()
。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ iterator
: 指向第一个元素的迭代器(非 const
版本)。
⚝ const_iterator
: 指向第一个元素的常量迭代器(const
版本)。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
for (auto it = sv.begin(); it != sv.end(); ++it) { // 使用 begin() 和 end() 遍历
8
std::cout << *it << " ";
9
}
10
std::cout << std::endl;
11
return 0;
12
}
输出:
1
10 20 30
要点(Key Points):
① 返回指向容器起始位置的迭代器,用于遍历容器。
② begin()
返回的迭代器类型取决于 SmallVector
对象是否为 const
。
③ 与 end()
配合使用,可以遍历整个容器。
5.5.2 end()
函数签名(Function Signature):
1
iterator end() noexcept;
2
const_iterator end() const noexcept;
描述(Description):
end()
函数返回指向 SmallVector
中最后一个元素之后位置的迭代器,即尾后迭代器(past-the-end iterator)。尾后迭代器本身不指向任何元素,通常用于表示遍历的结束。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ iterator
: 尾后迭代器(非 const
版本)。
⚝ const_iterator
: 尾后常量迭代器(const
版本)。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
for (auto it = sv.begin(); it != sv.end(); ++it) { // 使用 begin() 和 end() 遍历
8
std::cout << *it << " ";
9
}
10
std::cout << std::endl;
11
return 0;
12
}
输出:
1
10 20 30
要点(Key Points):
① 返回指向容器末尾之后位置的迭代器,用于判断遍历是否结束。
② end()
返回的迭代器类型取决于 SmallVector
对象是否为 const
。
③ 尾后迭代器不能解引用。
5.5.3 cbegin()
函数签名(Function Signature):
1
const_iterator cbegin() const noexcept;
描述(Description):
cbegin()
函数返回指向 SmallVector
中第一个元素的常量迭代器(const_iterator)。即使 SmallVector
对象本身不是 const
,cbegin()
也总是返回常量迭代器,用于只读遍历。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ const_iterator
: 指向第一个元素的常量迭代器。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
for (auto it = sv.cbegin(); it != sv.cend(); ++it) { // 使用 cbegin() 和 cend() 遍历
8
std::cout << *it << " "; // 只能读取元素,不能修改
9
// *it = 100; // 错误:常量迭代器不能修改元素
10
}
11
std::cout << std::endl;
12
return 0;
13
}
输出:
1
10 20 30
要点(Key Points):
① 返回常量迭代器,用于只读遍历,保证不会意外修改元素。
② cbegin()
总是返回 const_iterator
,即使对非 const
对象调用。
③ 与 cend()
配合使用,进行只读遍历。
5.5.4 cend()
函数签名(Function Signature):
1
const_iterator cend() const noexcept;
描述(Description):
cend()
函数返回指向 SmallVector
中最后一个元素之后位置的常量迭代器(const_iterator),即尾后常量迭代器(const_past-the-end iterator)。即使 SmallVector
对象本身不是 const
,cend()
也总是返回常量迭代器,用于只读遍历。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ const_iterator
: 尾后常量迭代器。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
for (auto it = sv.cbegin(); it != sv.cend(); ++it) { // 使用 cbegin() 和 cend() 遍历
8
std::cout << *it << " ";
9
}
10
std::cout << std::endl;
11
return 0;
12
}
输出:
1
10 20 30
要点(Key Points):
① 返回尾后常量迭代器,用于只读遍历的结束判断。
② cend()
总是返回 const_iterator
,即使对非 const
对象调用。
③ 尾后常量迭代器不能解引用。
5.5.5 rbegin()
函数签名(Function Signature):
1
reverse_iterator rbegin() noexcept;
2
const_reverse_iterator rbegin() const noexcept;
描述(Description):
rbegin()
函数返回指向 SmallVector
中最后一个元素的反向迭代器(reverse_iterator)。反向迭代器从容器的末尾开始向前遍历。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ reverse_iterator
: 指向最后一个元素的反向迭代器(非 const
版本)。
⚝ const_reverse_iterator
: 指向最后一个元素的常量反向迭代器(const
版本)。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
for (auto it = sv.rbegin(); it != sv.rend(); ++it) { // 使用 rbegin() 和 rend() 反向遍历
8
std::cout << *it << " ";
9
}
10
std::cout << std::endl;
11
return 0;
12
}
输出:
1
30 20 10
要点(Key Points):
① 返回反向迭代器,用于从后向前遍历容器。
② rbegin()
返回的迭代器类型取决于 SmallVector
对象是否为 const
。
③ 与 rend()
配合使用,进行反向遍历。
5.5.6 rend()
函数签名(Function Signature):
1
reverse_iterator rend() noexcept;
2
const_reverse_iterator rend() const noexcept;
描述(Description):
rend()
函数返回指向 SmallVector
中第一个元素之前位置的反向迭代器(reverse_iterator),即反向尾后迭代器(reverse_past-the-end iterator)。反向尾后迭代器本身不指向任何元素,通常用于表示反向遍历的结束。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ reverse_iterator
: 反向尾后迭代器(非 const
版本)。
⚝ const_reverse_iterator
: 反向尾后常量迭代器(const
版本)。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
for (auto it = sv.rbegin(); it != sv.rend(); ++it) { // 使用 rbegin() 和 rend() 反向遍历
8
std::cout << *it << " ";
9
}
10
std::cout << std::endl;
11
return 0;
12
}
输出:
1
30 20 10
要点(Key Points):
① 返回反向尾后迭代器,用于反向遍历的结束判断。
② rend()
返回的迭代器类型取决于 SmallVector
对象是否为 const
。
③ 反向尾后迭代器不能解引用。
5.5.7 crbegin()
函数签名(Function Signature):
1
const_reverse_iterator crbegin() const noexcept;
描述(Description):
crbegin()
函数返回指向 SmallVector
中最后一个元素的常量反向迭代器(const_reverse_iterator)。即使 SmallVector
对象本身不是 const
,crbegin()
也总是返回常量反向迭代器,用于只读反向遍历。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ const_reverse_iterator
: 指向最后一个元素的常量反向迭代器。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
for (auto it = sv.crbegin(); it != sv.crend(); ++it) { // 使用 crbegin() 和 crend() 反向只读遍历
8
std::cout << *it << " "; // 只能读取元素,不能修改
9
// *it = 100; // 错误:常量反向迭代器不能修改元素
10
}
11
std::cout << std::endl;
12
return 0;
13
}
输出:
1
30 20 10
要点(Key Points):
① 返回常量反向迭代器,用于只读反向遍历,保证不会意外修改元素。
② crbegin()
总是返回 const_reverse_iterator
,即使对非 const
对象调用。
③ 与 crend()
配合使用,进行只读反向遍历。
5.5.8 crend()
函数签名(Function Signature):
1
const_reverse_iterator crend() const noexcept;
描述(Description):
crend()
函数返回指向 SmallVector
中第一个元素之前位置的常量反向迭代器(const_reverse_iterator),即反向尾后常量迭代器(const_reverse_past-the-end iterator)。即使 SmallVector
对象本身不是 const
,crend()
也总是返回常量反向迭代器,用于只读反向遍历。
参数(Parameters):
无(None)
返回值(Return Value):
⚝ const_reverse_iterator
: 反向尾后常量迭代器。
异常(Exceptions):
noexcept
- 此函数保证不抛出任何异常。
代码示例(Code Example):
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
int main() {
5
folly::SmallVector<int, 4> sv = {10, 20, 30};
6
7
for (auto it = sv.crbegin(); it != sv.crend(); ++it) { // 使用 crbegin() 和 crend() 反向只读遍历
8
std::cout << *it << " ";
9
}
10
std::cout << std::endl;
11
return 0;
12
}
输出:
1
30 20 10
要点(Key Points):
① 返回反向尾后常量迭代器,用于只读反向遍历的结束判断。
② crend()
总是返回 const_reverse_iterator
,即使对非 const
对象调用。
③ 反向尾后常量迭代器不能解引用。
END_OF_CHAPTER
6. chapter 6: SmallVector 常见问题与最佳实践(SmallVector Common Problems and Best Practices)
6.1 常见错误用法与陷阱(Common Misuse and Pitfalls):避免 SmallVector 使用中的常见错误
SmallVector 作为一种高效且灵活的容器,在许多场景下都能提供优异的性能。然而,不当的使用方式可能会导致性能下降,甚至引入难以调试的错误。本节将深入探讨 SmallVector 常见的错误用法与陷阱,帮助读者避免这些问题,写出更健壮、更高效的代码。
6.1.1 未充分理解 Capacity 模板参数的影响(Insufficient Understanding of Capacity Template Parameter)
SmallVector 最核心的特性之一就是其固定的栈上容量 Capacity
。初学者最容易犯的错误就是没有充分理解 Capacity
模板参数的意义,导致在不合适的场景下使用了 SmallVector,或者设置了不合理的 Capacity
值。
① 误用场景:大规模数据存储
SmallVector 的设计初衷是为了处理小规模数据,当数据规模超过 Capacity
时,SmallVector 会退化为在堆上分配内存,此时其性能优势会大打折扣。如果需要存储大规模数据,std::vector
或 FBVector
等动态数组才是更合适的选择。
② Capacity
设置过小
如果 Capacity
设置得过小,频繁超出栈上容量会导致 SmallVector 频繁地进行堆内存分配和数据拷贝,这会显著降低性能,甚至比 std::vector
更慢。
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
#include <chrono>
4
#include <vector>
5
6
using namespace folly;
7
using namespace std;
8
using namespace std::chrono;
9
10
int main() {
11
// Capacity 设置过小的 SmallVector
12
SmallVector<int, 4> sv_small_capacity;
13
vector<int> vec;
14
15
auto start_sv = high_resolution_clock::now();
16
for (int i = 0; i < 10000; ++i) {
17
sv_small_capacity.push_back(i); // 频繁堆分配
18
}
19
auto end_sv = high_resolution_clock::now();
20
auto duration_sv = duration_cast<microseconds>(end_sv - start_sv);
21
22
auto start_vec = high_resolution_clock::now();
23
for (int i = 0; i < 10000; ++i) {
24
vec.push_back(i);
25
}
26
auto end_vec = high_resolution_clock::now();
27
auto duration_vec = duration_cast<microseconds>(end_vec - start_vec);
28
29
cout << "SmallVector (small capacity) push_back duration: " << duration_sv.count() << " microseconds" << endl;
30
cout << "std::vector push_back duration: " << duration_vec.count() << " microseconds" << endl;
31
32
return 0;
33
}
在这个例子中,SmallVector
的 Capacity
设置为 4,当循环次数达到 10000 时,SmallVector
会频繁进行堆分配,导致性能下降。std::vector
由于其动态扩容机制,性能表现反而更好。
③ Capacity
设置过大
虽然 Capacity
设置过小会频繁触发堆分配,但 Capacity
也不是越大越好。过大的 Capacity
会占用更多的栈空间。栈空间通常是有限的资源,过量占用栈空间可能导致栈溢出(Stack Overflow)的风险,尤其是在递归调用或多线程环境下。此外,即使没有栈溢出,过大的栈上对象也会增加函数调用的开销,因为函数调用时需要在栈上分配空间。
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
using namespace folly;
5
using namespace std;
6
7
void large_small_vector() {
8
// Capacity 设置过大的 SmallVector
9
SmallVector<int, 1024 * 1024> sv_large_capacity; // 占用 4MB 栈空间 (假设 int 4 字节)
10
cout << "Large SmallVector created on stack." << endl;
11
}
12
13
int main() {
14
large_small_vector();
15
return 0;
16
}
如果 large_small_vector
函数被频繁调用,或者在栈空间有限的环境下,就可能引发栈溢出问题。
最佳实践:
⚝ 评估数据规模:在使用 SmallVector 之前,务必评估你的数据规模。如果数据规模通常较小且可预测,SmallVector 是一个很好的选择。如果数据规模波动较大或可能很大,std::vector
或其他动态容器可能更合适。
⚝ 合理设置 Capacity
:根据实际应用场景,选择一个合适的 Capacity
值。Capacity
应该足够大,以容纳绝大多数情况下的数据,同时又不能过大,以免浪费栈空间。可以通过性能测试和 профилирование (profiling) 来找到最佳的 Capacity
值。
⚝ 考虑使用 constexpr
Capacity:如果 Capacity
在编译时是已知的,可以使用 constexpr
来定义 Capacity
,这样可以进一步提升性能,并允许编译器进行更多的优化。
6.1.2 迭代器失效问题(Iterator Invalidation Issues)
与 std::vector
类似,SmallVector 在进行某些操作时,也可能导致迭代器失效。理解迭代器失效的场景,并编写避免迭代器失效的代码,是使用 SmallVector 的重要一环。
① 插入元素导致迭代器失效
当向 SmallVector 中插入元素时,如果插入位置之前的元素数量加上插入元素数量超过了 Capacity
,SmallVector 可能会发生内存重分配(从栈上到堆上,或者堆上重新分配更大的空间)。内存重分配会导致原有内存块失效,因此指向原有内存块的迭代器也会失效。即使没有内存重分配,在 insert
操作之后,插入位置及其后的所有元素的迭代器都可能失效,因为元素的位置发生了移动。
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
using namespace folly;
5
using namespace std;
6
7
int main() {
8
SmallVector<int, 4> sv = {1, 2, 3, 4};
9
auto it = sv.begin();
10
++it; // 指向元素 2
11
12
sv.insert(sv.begin(), 0); // 在头部插入元素,可能导致迭代器失效
13
14
// 错误用法:使用可能失效的迭代器
15
// cout << *it << endl; // 可能崩溃或输出错误结果
16
17
// 正确用法:重新获取迭代器
18
it = sv.begin();
19
++it;
20
cout << *it << endl; // 正确输出 2
21
22
return 0;
23
}
在 insert
操作之后,之前的迭代器 it
可能已经失效。尝试解引用失效的迭代器会导致未定义行为。正确的做法是在 insert
操作后,重新获取迭代器。
② 删除元素导致迭代器失效
从 SmallVector 中删除元素,特别是使用 erase
函数删除元素时,被删除元素及其后的所有元素的迭代器都会失效。
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
using namespace folly;
5
using namespace std;
6
7
int main() {
8
SmallVector<int, 4> sv = {1, 2, 3, 4, 5};
9
auto it = sv.begin();
10
++it; // 指向元素 2
11
12
sv.erase(sv.begin()); // 删除头部元素,导致迭代器失效
13
14
// 错误用法:使用可能失效的迭代器
15
// cout << *it << endl; // 可能崩溃或输出错误结果
16
17
// 正确用法:重新获取迭代器或使用 erase 的返回值
18
it = sv.begin(); // 重新获取迭代器
19
cout << *it << endl; // 正确输出 2
20
21
// 或者使用 erase 的返回值,erase 返回指向被删除元素之后元素的迭代器
22
sv = {1, 2, 3, 4, 5};
23
it = sv.begin();
24
++it;
25
it = sv.erase(sv.begin()); // it 指向原元素 2 的位置,现在是元素 2 的下一个元素,即原元素 3
26
27
cout << *it << endl; // 正确输出 3 (如果容器不为空)
28
29
return 0;
30
}
erase
操作会使被删除元素及其后的元素的迭代器失效。为了安全地继续使用迭代器,可以重新获取迭代器,或者使用 erase
函数的返回值,它返回指向被删除元素之后元素的有效迭代器。
最佳实践:
⚝ 谨慎使用迭代器:在进行可能导致迭代器失效的操作(如 insert
, erase
, push_back
等)之后,不要继续使用之前的迭代器,除非你非常清楚迭代器仍然有效。
⚝ 使用基于索引的循环:在某些情况下,可以使用基于索引的循环来代替迭代器循环,这样可以避免迭代器失效的问题。但是,基于索引的循环可能不如迭代器循环灵活。
⚝ 关注 API 文档:仔细阅读 SmallVector 的 API 文档,了解哪些操作可能导致迭代器失效,以及失效的具体规则。
6.1.3 拷贝与移动语义的误用(Misuse of Copy and Move Semantics)
C++11 引入了移动语义,旨在减少不必要的拷贝操作,提升性能。SmallVector 也充分利用了移动语义。然而,如果对拷贝和移动语义理解不足,可能会导致性能下降,甚至产生逻辑错误。
① 不必要的拷贝
在某些情况下,本可以进行移动操作的场景,却错误地进行了拷贝操作,导致性能损失。例如,当向 SmallVector 中插入一个临时对象时,应该使用移动语义,但如果代码写成了拷贝语义,就会产生额外的开销。
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
#include <string>
4
5
using namespace folly;
6
using namespace std;
7
8
struct MyString {
9
string data;
10
MyString(const string& s) : data(s) {
11
cout << "MyString copy constructor called" << endl;
12
}
13
MyString(string&& s) : data(move(s)) {
14
cout << "MyString move constructor called" << endl;
15
}
16
};
17
18
int main() {
19
SmallVector<MyString, 4> sv;
20
string temp_str = "hello";
21
22
cout << "Pushing back a temporary string (copy):" << endl;
23
sv.push_back(MyString(temp_str)); // 调用拷贝构造函数
24
25
cout << "\nPushing back a temporary string (move):" << endl;
26
sv.emplace_back(temp_str); // 调用移动构造函数 (in-place 构造)
27
28
return 0;
29
}
在第一个 push_back
中,由于使用了 MyString(temp_str)
显式构造了一个临时对象,但 push_back
接收的是左值引用,因此会调用拷贝构造函数。而在 emplace_back
中,直接在 SmallVector 内部构造对象,并使用了移动语义,避免了拷贝。
② 移动后对象的状态
移动操作会将源对象的状态转移到目标对象,移动后的源对象通常处于有效但未指定的状态。这意味着,移动后的源对象仍然可以析构,但不能再安全地访问其内容。如果错误地使用了移动后的源对象,可能会导致未定义行为。
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
#include <string>
4
5
using namespace folly;
6
using namespace std;
7
8
int main() {
9
SmallVector<string, 4> sv1 = {"hello", "world"};
10
SmallVector<string, 4> sv2 = move(sv1); // 移动 sv1 到 sv2
11
12
// 错误用法:访问移动后的 sv1
13
// cout << sv1[0] << endl; // 未定义行为
14
15
cout << "sv2[0]: " << sv2[0] << endl; // 正确访问 sv2
16
17
return 0;
18
}
在移动操作 move(sv1)
后,sv1
的状态变得未指定,访问 sv1
的元素是未定义行为。应该只访问移动后的目标对象 sv2
。
最佳实践:
⚝ 理解拷贝与移动语义:深入理解 C++ 的拷贝构造函数、移动构造函数、拷贝赋值运算符和移动赋值运算符,以及它们在不同场景下的调用时机。
⚝ 优先使用移动语义:在可以进行移动操作的场景下,优先使用移动语义,例如使用 std::move
显式移动,或者使用 emplace_back
等 in-place 构造函数。
⚝ 避免使用移动后的源对象:在移动操作后,不要再访问源对象的内容,除非你明确知道源对象的状态是可用的。
6.1.4 异常安全性的考虑(Exception Safety Considerations)
异常安全性是编写健壮 C++ 代码的重要方面。SmallVector 在设计时考虑了异常安全性,但开发者在使用 SmallVector 时,仍然需要注意异常安全编程的实践。
① 强异常安全保证
SmallVector 的某些操作,例如 push_back
, emplace_back
, insert
等,在分配内存或构造元素时,可能会抛出异常。在这些操作中,SmallVector 通常提供强异常安全保证,即如果操作抛出异常,程序的状态不会发生改变("commit or rollback")。例如,如果 push_back
在分配内存时抛出异常,SmallVector 的状态仍然保持不变,之前 push_back 的元素不会被添加到 SmallVector 中。
② 基本异常安全保证
SmallVector 的另一些操作,例如 erase
, pop_back
, clear
等,通常提供基本异常安全保证,即如果操作抛出异常,程序不会崩溃,资源不会泄漏,但程序的状态可能发生改变。例如,如果 erase
操作在删除元素的过程中抛出异常,SmallVector 的状态可能是不一致的,但程序仍然可以继续运行。
③ noexcept 操作
SmallVector 的某些操作被标记为 noexcept
,例如移动构造函数、移动赋值运算符、析构函数等。这意味着这些操作保证不会抛出异常。noexcept
操作对于编写异常安全的代码非常重要,因为它可以简化异常处理逻辑,并允许编译器进行更多的优化。
最佳实践:
⚝ 了解异常安全级别:了解 SmallVector 各个 API 的异常安全级别(强异常安全、基本异常安全、noexcept),以便在编写代码时做出正确的异常处理决策。
⚝ RAII 原则:遵循 RAII (Resource Acquisition Is Initialization) 原则,使用对象来管理资源,确保资源在异常情况下也能被正确释放。SmallVector 本身就是一个 RAII 容器,它负责管理其内部的内存资源。
⚝ 编写异常安全的代码:在编写使用 SmallVector 的代码时,要考虑异常情况,并编写异常安全的代码,例如使用 try-catch 块捕获和处理异常,避免资源泄漏和程序崩溃。
6.2 性能调优技巧(Performance Tuning Tips):提升 SmallVector 应用性能的实用技巧
SmallVector 的设计目标之一就是高性能。为了充分发挥 SmallVector 的性能优势,开发者需要掌握一些性能调优技巧。本节将介绍一些实用的 SmallVector 性能调优技巧,帮助读者写出更高效的代码。
6.2.1 合理预估并设置 Capacity(Estimating and Setting Capacity Appropriately)
如前所述,Capacity
模板参数是 SmallVector 性能的关键。合理预估并设置 Capacity
是性能调优的首要步骤。
① 统计分析
通过统计分析实际应用场景中的数据规模,可以预估 SmallVector 需要的 Capacity
。例如,如果已知在 99% 的情况下,数据规模不会超过 16,那么可以将 Capacity
设置为 16。
② профилирование (Profiling)
使用性能 профилирование 工具(如 Google Performance Tools, Valgrind 等)来分析程序的性能瓶颈。通过 профилирование,可以了解 SmallVector 的内存分配情况,以及是否频繁发生堆分配。根据 профилирование 结果,调整 Capacity
值,直到性能达到最佳。
③ 动态调整 Capacity (谨慎使用)
在某些特殊情况下,如果无法预先确定最佳 Capacity
,可以考虑动态调整 Capacity
。例如,可以先使用一个较小的 Capacity
,当 SmallVector 发生堆分配时,再动态地增加 Capacity
。但是,动态调整 Capacity
会增加代码的复杂性,并且可能引入新的性能问题,因此需要谨慎使用。
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
4
using namespace folly;
5
using namespace std;
6
7
template <typename T, size_t InitialCapacity>
8
class DynamicCapacitySmallVector : public SmallVector<T, InitialCapacity> {
9
public:
10
using Base = SmallVector<T, InitialCapacity>;
11
using Base::Base;
12
13
void push_back(const T& value) {
14
if (this->full()) {
15
// 动态增加 Capacity (例如翻倍)
16
this->reserve(this->capacity() * 2); // 堆分配
17
}
18
Base::push_back(value);
19
}
20
21
void push_back(T&& value) {
22
if (this->full()) {
23
this->reserve(this->capacity() * 2);
24
}
25
Base::push_back(move(value));
26
}
27
};
28
29
int main() {
30
DynamicCapacitySmallVector<int, 4> dyn_sv;
31
for (int i = 0; i < 100; ++i) {
32
dyn_sv.push_back(i); // 初始 Capacity 为 4,超出后动态扩容
33
}
34
cout << "DynamicCapacitySmallVector size: " << dyn_sv.size() << ", capacity: " << dyn_sv.capacity() << endl;
35
36
return 0;
37
}
DynamicCapacitySmallVector
类继承自 SmallVector
,并在 push_back
操作中动态地增加 Capacity
。这种方法可以应对数据规模不确定的情况,但需要权衡性能和复杂性。
最佳实践:
⚝ 优先静态 Capacity:尽可能在编译时确定 Capacity
,并使用静态 Capacity
的 SmallVector。
⚝ 合理预估 Capacity:通过统计分析和 профилирование,合理预估并设置 Capacity
。
⚝ 谨慎动态 Capacity:只有在必要时才考虑动态调整 Capacity
,并仔细评估其性能影响。
6.2.2 使用移动语义减少拷贝(Using Move Semantics to Reduce Copies)
移动语义是提升 SmallVector 性能的有效手段。在元素插入、赋值等操作中,尽可能使用移动语义,可以减少不必要的拷贝开销。
① emplace_back
和 emplace
使用 emplace_back
和 emplace
函数进行元素插入,可以直接在 SmallVector 内部构造元素,避免临时对象的产生和拷贝。
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
#include <string>
4
5
using namespace folly;
6
using namespace std;
7
8
struct MyObject {
9
string data;
10
MyObject(const string& s) : data(s) {
11
cout << "MyObject copy constructor" << endl;
12
}
13
MyObject(string&& s) : data(move(s)) {
14
cout << "MyObject move constructor" << endl;
15
}
16
};
17
18
int main() {
19
SmallVector<MyObject, 4> sv;
20
string temp_str = "hello";
21
22
cout << "push_back (copy):" << endl;
23
sv.push_back(MyObject(temp_str)); // 拷贝构造
24
25
cout << "\nemplace_back (move):" << endl;
26
sv.emplace_back(temp_str); // 移动构造 (in-place)
27
28
return 0;
29
}
emplace_back
直接在 SmallVector 内部构造 MyObject
对象,调用的是移动构造函数,避免了拷贝。
② std::move
在将对象插入或赋值给 SmallVector 时,可以使用 std::move
将对象转换为右值引用,强制进行移动操作。
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
#include <string>
4
5
using namespace folly;
6
using namespace std;
7
8
int main() {
9
SmallVector<string, 4> sv1 = {"hello"};
10
SmallVector<string, 4> sv2;
11
12
cout << "Copy assignment:" << endl;
13
sv2 = sv1; // 拷贝赋值
14
15
cout << "\nMove assignment:" << endl;
16
SmallVector<string, 4> sv3;
17
sv3 = move(sv1); // 移动赋值
18
19
return 0;
20
}
move(sv1)
将 sv1
转换为右值引用,使得赋值操作 sv3 = move(sv1)
调用移动赋值运算符,而不是拷贝赋值运算符。
最佳实践:
⚝ 优先使用 emplace_back
和 emplace
:在插入元素时,尽可能使用 emplace_back
和 emplace
。
⚝ 显式使用 std::move
:在需要移动语义的场景下,显式使用 std::move
。
⚝ 自定义类型的移动语义:对于自定义类型,确保正确实现移动构造函数和移动赋值运算符,以便 SmallVector 可以充分利用移动语义。
6.2.3 避免不必要的内存分配(Avoiding Unnecessary Memory Allocations)
内存分配是昂贵的操作。SmallVector 的栈上存储特性可以减少堆内存分配,但仍然需要注意避免不必要的内存分配。
① reserve
预分配空间
如果预先知道 SmallVector 大概需要存储多少元素,可以使用 reserve
函数预先分配足够的空间。reserve
可以减少因容量不足而导致的内存重分配次数。
1
#include <folly/container/SmallVector.h>
2
#include <iostream>
3
#include <vector>
4
#include <chrono>
5
6
using namespace folly;
7
using namespace std;
8
using namespace std::chrono;
9
10
int main() {
11
// 不使用 reserve
12
SmallVector<int, 16> sv1;
13
auto start1 = high_resolution_clock::now();
14
for (int i = 0; i < 1000; ++i) {
15
sv1.push_back(i);
16
}
17
auto end1 = high_resolution_clock::now();
18
auto duration1 = duration_cast<microseconds>(end1 - start1);
19
20
// 使用 reserve 预分配空间
21
SmallVector<int, 16> sv2;
22
sv2.reserve(1000); // 预分配 1000 个元素的空间 (堆上)
23
auto start2 = high_resolution_clock::now();
24
for (int i = 0; i < 1000; ++i) {
25
sv2.push_back(i);
26
}
27
auto end2 = high_resolution_clock::now();
28
auto duration2 = duration_cast<microseconds>(end2 - start2);
29
30
cout << "Without reserve duration: " << duration1.count() << " microseconds" << endl;
31
cout << "With reserve duration: " << duration2.count() << " microseconds" << endl;
32
33
return 0;
34
}
在循环插入大量元素之前,使用 reserve
预分配空间,可以显著减少内存重分配的次数,提升性能。
② 避免频繁的 insert
和 erase
操作
insert
和 erase
操作通常需要移动元素,如果频繁进行这些操作,会产生额外的开销。在性能敏感的场景下,应尽量避免频繁的 insert
和 erase
操作。如果需要频繁进行插入和删除操作,可以考虑使用 std::deque
或 std::list
等其他容器。
③ shrink_to_fit
释放多余空间
当 SmallVector 的容量远大于实际元素数量时,可以使用 shrink_to_fit
函数释放多余的堆内存空间。shrink_to_fit
可以减少内存占用,但可能会导致内存重分配,因此需要权衡使用。
最佳实践:
⚝ 使用 reserve
预分配空间:在预知数据规模的情况下,使用 reserve
预分配空间。
⚝ 减少 insert
和 erase
操作:尽量避免频繁的 insert
和 erase
操作。
⚝ 谨慎使用 shrink_to_fit
:在内存敏感的场景下,可以考虑使用 shrink_to_fit
释放多余空间,但需要注意其性能影响。
6.2.4 自定义分配器 (Custom Allocator) (高级技巧)
对于高级用户,可以考虑使用自定义分配器 (Custom Allocator) 来进一步优化 SmallVector 的内存管理。自定义分配器可以实现更精细的内存控制,例如使用内存池、共享内存等。
① 内存池分配器
内存池分配器可以预先分配一大块内存,然后从中分配小块内存。内存池分配器可以减少内存分配和释放的开销,提高内存分配效率。
② 共享内存分配器
在多进程或跨进程通信的场景下,可以使用共享内存分配器,将 SmallVector 的数据存储在共享内存中,实现进程间的数据共享。
最佳实践:
⚝ 了解自定义分配器:深入了解 C++ 的自定义分配器机制,以及其在内存管理中的作用。
⚝ 根据场景选择分配器:根据实际应用场景,选择合适的自定义分配器,例如内存池分配器、共享内存分配器等。
⚝ 谨慎使用自定义分配器:自定义分配器是高级技巧,使用不当可能会引入新的问题。只有在对内存管理有深入理解,并且性能瓶颈确实在内存分配时,才考虑使用自定义分配器。
6.3 与其他容器的选择:std::vector, std::array, std::deque 等(Choosing between SmallVector and other containers: std::vector, std::array, std::deque, etc.):根据不同场景选择最合适的容器
C++ 标准库提供了多种容器,每种容器都有其特定的适用场景。SmallVector 虽然在某些场景下具有优势,但并非万能容器。本节将 SmallVector 与 std::vector
, std::array
, std::deque
等常用容器进行对比,帮助读者根据不同场景选择最合适的容器。
6.3.1 SmallVector vs std::vector
std::vector
是 C++ 标准库中最常用的动态数组容器。SmallVector 与 std::vector
都是动态数组,但在内存分配策略上有所不同。
特性 | SmallVector | std::vector |
---|---|---|
内存分配 | 栈上 + 堆上 | 堆上 |
容量 | 编译时固定栈上容量,运行时动态堆容量 | 运行时动态堆容量 |
性能 | 小规模数据栈上分配,性能高;大规模数据堆分配,性能接近 std::vector | 堆分配,性能稳定,但小规模数据可能略逊于 SmallVector |
适用场景 | 小规模数据为主,偶尔有大规模数据的场景 | 大规模数据为主,数据规模波动较大的场景 |
栈溢出风险 | 有,Capacity 过大可能导致栈溢出 | 无 |
编译时容量优化 | 可以通过 constexpr Capacity 进行编译时优化 | 无 |
选择建议:
⚝ 优先 SmallVector
(小规模数据):如果数据规模通常较小(例如几十个元素以内),且对性能要求较高,优先选择 SmallVector。
⚝ std::vector
(大规模数据):如果数据规模较大或波动较大,或者不确定数据规模,选择 std::vector
更安全可靠。
⚝ 混合使用:在某些复杂场景下,可以混合使用 SmallVector 和 std::vector
。例如,可以使用 SmallVector 作为局部变量,处理小规模数据;使用 std::vector
作为类成员变量,处理大规模数据。
6.3.2 SmallVector vs std::array
std::array
是 C++ 标准库中的固定大小数组容器。std::array
的大小在编译时确定,存储在栈上,与 C 风格数组类似,但提供了更安全的接口和标准库容器的便利性。
特性 | SmallVector | std::array |
---|---|---|
内存分配 | 栈上 + 堆上 | 栈上 |
容量 | 编译时固定栈上容量,运行时动态堆容量 | 编译时固定大小 |
大小可变性 | 运行时可动态增长 (超出 Capacity 后) | 编译时固定大小,不可动态增长 |
性能 | 小规模数据栈上分配,性能高;可动态增长 | 栈上分配,性能极高,编译时大小确定 |
适用场景 | 小规模数据为主,偶尔需要动态增长的场景 | 编译时大小已知,不需要动态增长的场景 |
栈溢出风险 | 有,Capacity 过大可能导致栈溢出 | 有,std::array 大小过大可能导致栈溢出 |
灵活性 | 比 std::array 更灵活,可动态增长 | 灵活性较低,大小固定 |
选择建议:
⚝ 优先 std::array
(固定大小):如果数组大小在编译时已知,且不需要动态增长,优先选择 std::array
。std::array
的性能通常是最高的,且类型安全。
⚝ SmallVector
(小规模动态):如果数据规模通常较小,但偶尔需要动态增长,选择 SmallVector。
⚝ 避免混淆:std::array
的大小是模板参数,必须在编译时确定;SmallVector 的 Capacity
也是模板参数,但超出 Capacity
后可以动态增长。不要将两者混淆。
6.3.3 SmallVector vs std::deque
std::deque
(双端队列) 是一种动态数组容器,支持在头部和尾部高效地插入和删除元素。std::deque
的内存分配策略与 std::vector
和 SmallVector 不同,std::deque
通常由多个独立的内存块组成。
特性 | SmallVector | std::deque |
---|---|---|
内存分配 | 栈上 + 堆上 | 多个堆内存块 |
容量 | 编译时固定栈上容量,运行时动态堆容量 | 运行时动态增长,分块分配 |
插入/删除 | 尾部高效插入/删除,中间插入/删除可能较慢 | 头部和尾部高效插入/删除,中间插入/删除可能较慢 |
内存连续性 | 栈上或堆上连续内存 | 内存不保证连续 |
迭代器失效 | 插入/删除可能导致迭代器失效 | 头部或尾部插入/删除可能导致迭代器失效 |
适用场景 | 小规模数据为主,偶尔有大规模数据,尾部操作为主 | 需要在头部和尾部频繁插入/删除元素的场景 |
性能 | 小规模数据栈上分配,性能高;尾部操作高效 | 头部和尾部操作高效,但随机访问性能可能略逊于 vector |
选择建议:
⚝ SmallVector
(小规模尾部操作):如果数据规模通常较小,且主要在尾部进行插入和删除操作,可以选择 SmallVector。
⚝ std::deque
(双端操作):如果需要在头部和尾部频繁进行插入和删除操作,std::deque
是更合适的选择。
⚝ std::vector
(随机访问):如果需要频繁进行随机访问,且插入和删除操作较少,std::vector
通常是更好的选择,因为 std::vector
的内存是连续的,随机访问性能更高。
6.3.4 其他容器
除了 std::vector
, std::array
, std::deque
之外,C++ 标准库还提供了其他容器,例如 std::list
, std::forward_list
, std::set
, std::map
等。这些容器各有特点,适用于不同的场景。
⚝ std::list
和 std::forward_list
(链表):适用于频繁在任意位置插入和删除元素的场景,但随机访问性能较差。
⚝ std::set
和 std::map
(关联容器):适用于需要存储键值对,并进行快速查找、插入和删除操作的场景,元素自动排序。
⚝ std::unordered_set
和 std::unordered_map
(哈希表):适用于需要快速查找、插入和删除操作的场景,元素无序,平均查找时间为常数时间。
通用选择原则:
⚝ 根据需求选择:根据实际应用场景的需求,例如数据规模、操作类型(插入、删除、查找、随机访问等)、性能要求、内存限制等,选择最合适的容器。
⚝ 性能 профилирование (Profiling):在性能敏感的场景下,使用性能 профилирование 工具来评估不同容器的性能,选择性能最佳的容器。
⚝ 代码可读性和维护性:在满足性能需求的前提下,也要考虑代码的可读性和维护性。选择最易于理解和维护的容器。
6.4 SmallVector 的未来发展趋势(Future Development Trends of SmallVector):展望 SmallVector 的发展方向和潜在改进
SmallVector 作为 Folly 库中的重要组件,在现代 C++ 开发中发挥着越来越重要的作用。随着 C++ 标准的不断发展和应用场景的日益复杂,SmallVector 也在不断演进和完善。本节将展望 SmallVector 的未来发展趋势,探讨其潜在的改进方向和应用前景。
6.4.1 C++ 标准化
目前 SmallVector 并非 C++ 标准库的一部分,而是 Folly 库的组件。未来,SmallVector 有可能被 C++ 标准化,成为标准库容器。这将极大地提升 SmallVector 的普及度和应用范围。
① 提案与讨论
C++ 标准委员会一直在关注 SmallVector 这类栈上小对象优化的容器。未来可能会有提案将 SmallVector 或类似的设计纳入 C++ 标准库。
② 标准库的演进
C++ 标准库也在不断演进,例如 C++20 引入了 std::span
等新的工具,为容器的扩展和优化提供了更多可能性。SmallVector 的标准化将是 C++ 标准库容器发展的重要方向之一。
6.4.2 Capacity 自适应与动态调整
当前的 SmallVector 的 Capacity
是模板参数,需要在编译时确定。未来的 SmallVector 可能会引入 Capacity 自适应和动态调整的机制,以更好地应对数据规模不确定的场景。
① 编译时 Capacity 推导
可以考虑在某些场景下,编译器能够自动推导 SmallVector 的最佳 Capacity
,例如根据初始化列表的大小、函数参数的类型等。
② 运行时 Capacity 调整策略
可以引入更智能的运行时 Capacity 调整策略,例如根据历史数据规模、内存使用情况等,动态地调整 SmallVector 的 Capacity。
6.4.3 与其他 Folly 组件的深度集成
SmallVector 作为 Folly 库的一部分,可以与其他 Folly 组件进行更深度的集成,发挥更大的作用。
① 与 FBString, FBVector 等协同优化
SmallVector 可以与 FBString, FBVector 等其他 Folly 容器协同优化,例如在内存分配、移动语义等方面进行更紧密的配合,提升整体性能。
② 与 Folly 算法库的集成
Folly 提供了丰富的算法库,SmallVector 可以与这些算法库进行更紧密的集成,提供更高效的算法实现,例如针对 SmallVector 特性优化的排序、查找等算法。
6.4.4 硬件加速与 SIMD 优化
随着硬件技术的不断发展,SIMD (Single Instruction, Multiple Data) 等硬件加速技术在现代处理器中得到广泛应用。未来的 SmallVector 可能会利用硬件加速技术,进一步提升性能。
① SIMD 优化
可以针对 SmallVector 的常用操作,例如元素拷贝、比较、算术运算等,进行 SIMD 优化,利用 SIMD 指令并行处理多个元素,提高数据处理速度。
② 硬件感知分配器
可以开发硬件感知的分配器,根据硬件特性(例如缓存大小、内存带宽等)优化 SmallVector 的内存分配策略,提高内存访问效率。
6.4.5 更丰富的 API 和功能
未来的 SmallVector 可能会增加更丰富的 API 和功能,以满足更广泛的应用需求。
① 范围 (Range) 支持
可以增加对 C++20 范围 (Range) 的支持,提供更方便的范围操作接口,例如范围构造、范围算法等。
② 并发 (Concurrency) 支持
可以考虑在 SmallVector 中引入并发支持,例如线程安全的插入、删除、访问等操作,以满足多线程编程的需求。
③ 与其他语言的互操作性
可以考虑增强 SmallVector 与其他语言(例如 Python, Java 等)的互操作性,方便在跨语言的系统中使用 SmallVector。
总结:
SmallVector 的未来发展前景广阔。随着 C++ 标准的演进、硬件技术的进步以及应用场景的拓展,SmallVector 将会不断完善和优化,在高性能 C++ 开发中发挥越来越重要的作用。开发者应持续关注 SmallVector 的发展动态,学习和掌握其最新特性和最佳实践,以便更好地利用 SmallVector 提升代码性能和效率。
END_OF_CHAPTER
7. chapter 7: 总结与展望(总结与展望)
7.1 SmallVector 的价值与意义回顾(SmallVector 的价值与意义回顾)
在本书的尾声,我们共同回顾了 Folly 库中 SmallVector
这一强大而精巧的工具。从初识 Folly 库,到深入 SmallVector
的设计哲学、核心概念、高级特性,再到实战应用与 API 全面解析,相信读者已经对 SmallVector
有了系统而深入的理解。现在,让我们再次聚焦 SmallVector
的价值与意义,总结其在现代 C++ 开发中的独特地位。
① 性能优势与效率提升:SmallVector
最核心的价值在于其在特定场景下卓越的性能表现。相较于 std::vector
,SmallVector
通过内联小尺寸数据的栈空间,显著减少了动态内存分配的开销。这在元素数量较小且可预测的场景中,例如:
▮▮▮▮ⓑ 频繁创建和销毁的小型容器:避免了堆内存的频繁分配和释放,降低了系统开销。
▮▮▮▮ⓒ 对性能敏感的应用:如高性能计算、实时系统、游戏开发等,栈上分配的快速性至关重要。
▮▮▮▮ⓓ 缓存友好性:栈内存的连续性有助于提高 CPU 缓存命中率,进一步提升性能。
② 内存管理的精细化控制:SmallVector
提供了对内存分配策略更精细的控制。通过模板参数 Capacity
,开发者可以静态地指定栈上空间的大小,从而在编译期就确定一部分内存布局。这种静态与动态内存管理的结合,使得 SmallVector
既能享受栈内存的快速性,又能应对数据量超出栈空间的动态增长需求。
③ 代码的简洁性与可读性:SmallVector
的 API 设计与 std::vector
高度一致,学习曲线平缓。开发者可以沿用 std::vector
的使用习惯,快速上手 SmallVector
,并将其无缝集成到现有的 C++ 项目中。这种熟悉的接口降低了代码维护成本,提升了开发效率。
④ 广泛的应用场景:SmallVector
并非仅仅是一个学术性的优化技巧,而是在工业界得到了广泛的应用。从 Facebook 的 Folly 库将其开源,到众多高性能项目对其青睐,都证明了 SmallVector
在实际工程中的价值。其应用场景涵盖:
▮▮▮▮ⓑ 嵌入式系统:资源受限的环境下,栈上分配的优势尤为突出。
▮▮▮▮ⓒ 网络编程:高效处理网络数据包,降低延迟。
▮▮▮▮ⓓ 游戏开发:优化游戏引擎数据结构,提升帧率。
▮▮▮▮ⓔ 高性能计算:加速计算密集型任务。
⑤ 现代 C++ 开发的最佳实践:SmallVector
体现了现代 C++ 开发中对性能、效率和资源控制的极致追求。它鼓励开发者深入理解内存管理机制,根据具体场景选择最合适的工具,编写出更加高效、健壮的代码。学习和掌握 SmallVector
,是提升 C++ 编程技能,迈向高级工程师和专家行列的重要一步。
总而言之,SmallVector
不仅仅是一个容器,更是一种设计思想的体现,一种性能优化的策略,一种现代 C++ 开发的最佳实践。它以其独特的优势,在众多场景下展现出强大的生命力,值得每一位 C++ 开发者深入学习和掌握。
7.2 持续学习与深入探索 Folly 库(持续学习与深入探索 Folly 库)
本书以 SmallVector
为切入点,带领读者领略了 Folly 库的冰山一角。然而,Folly 库作为一个由 Facebook 开源的、经过大规模工业实践检验的 C++ 库,其蕴含的知识和技术远不止于此。持续学习和深入探索 Folly 库,将为你的 C++ 技能提升和职业发展带来无限可能。
① 深入探索 Folly 其他组件:Folly 库包含了大量的实用组件,例如:
⚝ FBString
:针对字符串操作优化的组件,提供了多种字符串类型和高效的字符串处理算法。
⚝ FBVector
:类似于 std::vector
,但在某些方面进行了优化,例如内存分配策略。
⚝ ConcurrentSkipListMap
:高效的并发跳跃列表映射,适用于高并发场景。
⚝ Futures
和 Promises
:用于异步编程的组件,简化了异步任务的管理和调度。
⚝ IO
框架:提供了高性能的 I/O 抽象层,支持多种 I/O 模型。
⚝ JSON
解析与生成:快速且易于使用的 JSON 处理库。
⚝ Logging
框架:灵活可配置的日志系统。
深入学习这些组件,可以扩展你的技术栈,提升解决复杂问题的能力。例如,学习 Futures
和 Promises
可以让你更好地应对并发编程挑战;掌握 IO
框架可以让你构建高性能的网络应用;了解 JSON
处理库可以让你更高效地处理数据交换格式。
② 关注 Folly 库的更新与发展:Folly 库是一个活跃的开源项目,不断进行更新和迭代。关注 Folly 库的 GitHub 仓库和社区动态,可以及时了解最新的特性、改进和最佳实践。参与社区讨论,甚至贡献代码,也是深入学习 Folly 库的有效途径。
③ 结合实际项目应用 Folly 库:理论学习固然重要,但真正的掌握来自于实践。尝试将 Folly 库应用到实际项目中,解决实际问题,才能更深刻地理解其设计思想和使用技巧。可以从小型项目开始,逐步尝试使用 Folly 库的各种组件,积累实战经验。
④ 拓展 C++ 知识体系:Folly 库的设计和实现,涉及了大量的 C++ 高级特性和编程技巧,例如:模板元编程、移动语义、完美转发、SFINAE(Substitution Failure Is Not An Error, 替换失败不是错误)、RAII(Resource Acquisition Is Initialization, 资源获取即初始化)等。学习 Folly 库的过程,也是一个深入学习和巩固 C++ 知识体系的过程。
⑤ 持续学习软件工程最佳实践:Folly 库不仅仅是一个技术库,更蕴含了 Facebook 等大型互联网公司在软件工程方面的最佳实践。学习 Folly 库的设计理念、代码风格、测试方法等,可以提升你的软件工程素养,培养良好的编程习惯。
学习永无止境,技术日新月异。希望本书能够成为你探索 Folly 库和现代 C++ 开发的起点。在未来的学习和工作中,保持好奇心,不断探索,勇于实践,你将在 C++ 的世界里取得更大的成就!🚀
END_OF_CHAPTER