%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
%%
# allocator 分配器相关
C++11 中,引入了两个类模版来提供统一的**动态内存管理**接口。
- `std::allocator`:分配器类,**封装了==分配、释放==动态内存的过程**。
- `std::allocator_traits`:分配器访问类,**提供了统一的、分配器类型无关的接口来 "访问分配器的属性与方法"**。
> [!faq] ❓为什么需要 allocator?
>
> `std::allocator` 将 "**内存分配**" 与 "**对象构造**" 进行了分离,其分配的内存是 "**==未构造的==**"。**同时,将 "对象析构" 与 "内存释放" 也进行了分离**。
>
> - `new` 将 "**内存分配**" 与 "**对象构造**" 组合在了一起;
> - `delete` 将 "**对象析构**" 与 "**内存释放**" 组合在了一起。
>
>
<br><br>
# `std::allocator` 类
> `std::allocator` 位于 `<memory>` 头文件
`std::allocator<T>` 是一个类模版,其提供了一种 "**类型感知**" 的**内存分配/释放接口**,封装了底层的内存管理细节[^1]。
allocator 分配内存时,会根据**对象类型**来确定**恰当的 "内存大小" 和 "==对齐位置=="**。
> [!info] `std::allocator` 是 STL 容器的**默认内存管理器**,负责**为容器中的对象分配原始内存,构造或析构对象**。
> 此外,STL 容器允许使用自定义的分配器,以实现特定的内存管理策略,例如使用池分配器来提供性能或跟踪内存使用情况。
> [!important] `std::allocator` 使用 **底层的 `operator new` 和 `operator delete`** 来进行内存分配和释放。
>
> 因此,当其分配内存失败时,其行为即 `operator new` 的行为——**会抛出 `std::bad_alloc` 异常**。
### 使用说明
![[_attachment/02-开发笔记/01-cpp/内存管理/cpp-allocator.assets/IMG-cpp-allocator-83249F8D5030D7F33DC9D1FD04D24E16.png|770]]
`std::allocator<>` 类没有成员变量,其包含四个成员函数:
- `.allocate(n)`:分配**足够存储 `n` 个元素的==未构造原始内存==**,返回指向这块内存的指针。
- `.construct(p, args)`:在`p`指向的**已分配的原始内存**上**构造一个对象**;
- `.destroy(p)`:**析构 `p` 所指对象**;
- `.deallocate(p, n)`:**释放** `p` 所指的,由 `allocate` 分配的内存;
使用方式为:
- 先通过 `.allocate()` **分配足够的原始内存**,然后使用 `.construct()` 在原始内存上**逐个构造对象**;
- 先通过 `.destroy()` **逐个析构对象**,最后通过 `.deallocate()` **释放所申请的所有内存**;
> [!caution] `construct()` 和 `destroy()` 在 C++20 中以及被移除,可有两种方式改写:
>
> - **推荐使用 `std::allocator_traits` 类的包装来访问分配器的属性和方法**,而不是直接使用分配器的成员函数。
> - 使用 `std::construct_at()` 直接在地址内存地址构造对象,替换 `.construct()`;<br>同时,通**过直接调用类类型的析构函数**来替代 `destroy()`。
>
使用示例:
```cpp title:allocator_exam.cpp
#include <memory>
#include <iostream>
int main() {
// 创建一个std::allocator<int>对象
std::allocator<int> alloc; // 可动态分配`int`类型的allocator对象.
// 申请足够的连续内存空间, 返回指向该内存空间的指针(元素int类型)
int* arr = alloc.allocate(5); // 可容纳5个int元素的连续内存空间. 返回指向这片内存空间的指针
// 在原始内存空间上构造元素
for (int i = 0; i < 5; ++i) {
// alloc.construct(arr + i, i); //c++20中已移除.
// c++20中, 使用`std::construct_at()`直接在指定内存地址构造对象
std::construct_at(arr + i, i);
}
// 使用数组...
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// 析构并释放内存
// for (int i = 0; i < 5; ++i) {
// alloc.destory(arr + i); // C++20中已移除.
// }
// 基本类型没有析构函数, 直接释放内存即可.
// 对于用户自定义的类类型, 手动调用析构函数进行释放: `arr[i].~Type();`
alloc.deallocate(arr, 5);
}
```
<br>
#### 拷贝和填充未初始化内存的算法
> 位于头文件 `<memory>`
标准库为 allocator 类提供了几个 "**伴随算法**",用于在**由 allocator 分配的、未初始化的 "指定内存地址"** 上 "**==构造元素==**"(以 "**拷贝构造**" 的方式),
![[_attachment/02-开发笔记/01-cpp/内存管理/cpp-allocator.assets/IMG-cpp-allocator-0F23429952C9F8CF38C0C5A11AB1C920.png|721]]
使用示例:
```cpp
vector<int> vec { 1, 2, 3, 4, 5, 6};
allocator<int> alloc;
auto p = alloc.allocate(vec.size() * 2); // 分配空间, vec.size()的2倍
auto q = unitialized_copy(vec.begin(), vec.end(), p); // 拷贝vec中的元素, 从p所指内存位置开始构造元素, 返回指向拷贝后最后一项元素之后位置的指针
uninitialized_fill_n(q, vi.size(), 42); // 从q所指内存位置, 以值42, 构造vi.size()个元素, 返回空.
```
<br><br><br>
# `std::allocator_traits` 类模版
`std::allocator_traits` 提供了**统一的、与分配器类型无关的接口**来**访问分配器类的各种成员属性和方法**。
通过使用该类,可以编写 "**与分配器类型无关**" 的代码,支持对 "**遵循 C++标准分配器接口的旧式分配器**" 以及 "**带有扩展接口的自定义分配器**" 的访问。
`std::allocator_traits<T>` 以 "**分配器类**" 作为模版参数,提供了一系列**静态成员类型别名和函数**,包括:
- 支持 "**查询分配器类型**" 的**属性**
- 如 `value_type`、`pointer`、`size_type` 等
- **包装了 "分配器操作"的静态成员函数**(与 `std::allocate` 的成员函数同名):
- `allocate`、 `deallocate`、 `construct`、 `destroy` 等;
示例:`std::allocator_traits` 与 `std::allocator` 的搭配使用
```cpp title:allocator_traits_exam.cpp
#include <memory> // 包含std::allocator和std::allocator_traits
#include <iostream>
int main() {
// 创建一个std::allocator<int>分配器实例
std::allocator<int> alloc; // 标准分配器
// 使用std::allocator_traits<> 来包装对std::allocator<int>的访问
// using traits = std::allocator_traits<std::allocator<int>>;
using traits = std::allocator_traits<decltype(alloc)>; // 与上语句等效
// 分配原始内存
size_t size = 5;
int* arr = traits::allocate(alloc, size); // 足以存储5个int对象的原始内存空间.
// 在分配的原始内存上构造对象.
for (int i = 0; i < size; ++i) {
// 参数: alloc, 对象的地址, 对象构造函数的参数
traits::construct(alloc, arr + i, i);
}
// 使用分配的对象
for (int i = 0; i < size; ++i) {
std::cout << "Value: " << arr[i] << std::endl;
}
// 查询分配器的属性
// typeid().name()返回的是编译器特定的类型名称, 通常不是"人类可读"的
// 实际应用中, 这些类型信息主要用于模版编程和类型推导中.
std::cout << "Pointer type:" << typeid(traits::pointer).name() << '\n';
std::cout << "Size type:" << typeid(traits::size_type).name() << '\n';
std::cout << "Difference type:" << typeid(traits::difference_type).name() << '\n';
std::cout << "Value Type: " << typeid(traits::value_type).name() << '\n';
// 析构对象
for (int i = 0; i < size; ++i) {
traits::destroy(alloc, arr + i);
}
// 释放内存
traits::deallocate(alloc, arr, size);
}
```
<br><br>
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
# ♾️参考资料
# Footnotes
[^1]: 《C++ Primer》P429