C++ 命名需求: 分配器
封装用于访问/寻址、分配/释放和对象构造/销毁的策略。
每个可能需要分配或释放存储空间的标准库组件,从 std::string、std::vector 和每个容器,除了 std::array(自 C++11 起) 和 std::inplace_vector(自 C++26 起),到 std::shared_ptr 和 std::function(直到 C++17),都是通过一个 分配器 完成的:一个满足以下要求的类类型对象。
许多分配器要求的实现是可选的,因为所有 可分配器感知容器 通过 std::allocator_traits 间接访问分配器,并且 std::allocator_traits 提供了这些要求的默认实现。
内容 |
[编辑] 需求
鉴于
-
T
,一个非 const、非引用类型(直到 C++11)非 const 对象类型(自 C++11 起)(直到 C++17)cv 无限定对象类型(自 C++17 起), -
A
,类型T
的一个 分配器 类型, - a,类型
A
的一个对象, -
B
,对应于某个 cv 无限定对象类型U
的 分配器 类型(通过重新绑定A
获得), - b,类型
B
的一个对象, - p,类型 std::allocator_traits<A>::pointer 的值,通过调用 std::allocator_traits<A>::allocate() 获得,
- cp,类型 std::allocator_traits<A>::const_pointer 的值,通过从 p 转换获得,
- vp,类型 std::allocator_traits<A>::void_pointer 的值,通过从 p 转换获得,
- cvp,类型 std::allocator_traits<A>::const_void_pointer 的值,通过从 cp 或 vp 转换获得,
- xp,指向某个 cv 无限定对象类型
X
的可解引用指针, - r,通过表达式 *p 获得的类型
T
的左值, - n,类型 std::allocator_traits<A>::size_type 的值。
类型标识 | 别名类型 | 需求 |
---|---|---|
A::pointer (可选) |
(未指定)[1] | |
A::const_pointer (可选) |
(未指定) | |
A::void_pointer (可选) |
(未指定) |
|
A::const_void_pointer (可选) |
(未指定) |
|
A::value_type
|
T
|
|
A::size_type (可选) |
(未指定) |
|
A::difference_type (可选) |
(未指定) |
|
A::template rebind<U>::other (可选)[2] |
B
|
|
表达式 | 返回类型 | 需求 |
---|---|---|
*p | T&
|
|
*cp | const T& | *cp 和 *p 标识相同的对象。 |
p->m | (原样) | 与 (*p).m 相同,如果 (*p).m 定义良好。 |
cp->m | (原样) | 与 (*cp).m 相同,如果 (*cp).m 定义良好。 |
static_cast<A::pointer>(vp) | (原样) | static_cast<A::pointer>(vp) == p |
static_cast<A::const_pointer>(cvp) | (原样) | static_cast<A::const_pointer>(cvp) == cp |
std::pointer_traits<A::pointer>::pointer_to(r) | (原样) |
表达式 | 返回类型 | 需求 |
---|---|---|
a.allocate(n) | A::pointer
|
分配适合于类型为 T[n] 的数组对象的存储空间,并创建数组,但不构造数组元素。可能会抛出异常。如果 n == 0,则返回值未定义。 |
a.allocate(n, cvp) (可选) | 与 a.allocate(n) 相同,但可能以未指定的方式使用 cvp (nullptr 或从 a.allocate() 获取的指针) 来帮助局部性。 | |
a.allocate_at_least(n) (可选) (自 C++23 起) | std::allocation_result <A::pointer> |
分配适合于类型为 T[cnt] 的数组对象的存储空间,并创建数组,但不构造数组元素,然后返回 {p, cnt},其中 p 指向存储空间,而 cnt 不小于 n。可能会抛出异常。 |
a.deallocate(p, n) | (未使用) | 释放 p 指向的存储空间,它必须是先前对 allocate 或 allocate_at_least (自 C++23 起) 的调用返回的值,该调用尚未被对 deallocate 的后续调用使无效。 n 必须与先前传递给 allocate 或介于通过 allocate_at_least 请求的元素数量和返回的元素数量之间(可以等于任一边界)(自 C++23 起) 的值匹配。不会抛出异常。 |
a.max_size() (可选) | A::size_type
|
可以传递给 A::allocate() 的最大值。 |
a.construct(xp, args...) (可选) | (未使用) | 使用 args... 作为构造函数参数,在先前分配的存储空间(位于 xp 指向的地址处)中构造类型为 X 的对象。 |
a.destroy(xp) (可选) | (未使用) | 销毁 xp 指向的类型为 X 的对象,但不释放任何存储空间。 |
表达式 | 返回类型 | 需求 |
---|---|---|
a1 == a2 | bool |
|
a1 != a2 |
| |
声明 | 效果 | 需求 |
A a1(a) | 复制构造 a1,使得 a1 == a。 (注意:每个 Allocator 也满足 CopyConstructible。) |
|
A a1 = a | ||
A a(b) | 构造 a,使得 B(a) == b 且 A(b) == a。 (注意:这意味着所有通过 rebind 关联的分配器都维护彼此的资源,例如内存池。)
|
|
A a1(std::move(a)) | 构造 a1,使得它等于 a 的先前值。 |
|
A a1 = std::move(a) | ||
A a(std::move(b)) | 构造 a,使得它等于 A(b) 的先前值。 |
|
类型标识 | 别名类型 | 需求 |
A::is_always_equal (可选) |
std::true_type 或 std::false_type 或从其派生。 |
|
表达式 | 返回类型 | 描述 |
---|---|---|
a.select_on_container_copy_construction() (可选) |
A
|
|
类型标识 | 别名类型 | 描述 |
A::propagate_on_container_copy_assignment (可选) |
std::true_type 或 std::false_type 或从其派生。 |
|
A::propagate_on_container_move_assignment (可选) |
| |
A::propagate_on_container_swap (可选) |
|
注意
- ↑ 另见下文的 特殊指针。
- ↑ 如果此分配器是形式为
SomeAllocator<T, Args>
的模板(其中Args
是零个或多个额外的模板类型参数),则rebind
才是可选的(由 std::allocator_traits 提供)。
鉴于
- x1 和 x2,类型为(可能不同)的
X::void_pointer
、X::const_void_pointer
、X::pointer
或X::const_pointer
的对象
- 然后,如果且仅当 x1 和 x2 都可以使用一系列的 static_cast(仅使用这四种类型)显式转换为类型为
X::const_pointer
的两个相应对象 px1 和 px2,并且表达式 px1 == px2 的计算结果为 true,则 x1 和 x2 是等值的指针值。
鉴于
- w1 和 w2,类型为
X::void_pointer
的对象
- 然后,对于表达式 w1 == w2 和 w1 != w2,可以使用等值的
X::const_void_pointer
类型对象替换其中一个或两个对象,语义不变。
鉴于
- p1 和 p2,类型为
X::pointer
的对象
- 然后,对于表达式 p1 == p2,p1 != p2,p1 < p2,p1 <= p2,p1 >= p2,p1 > p2,p1 - p2,可以使用等值的
X::const_pointer
类型对象替换其中一个或两个对象,语义不变。
上述要求使得比较 容器 的 iterator
和 const_iterator
成为可能。
分配器完整性要求如果以下两点都成立,则类型
|
(自 C++17 起) |
[编辑] 有状态和无状态分配器
每个 分配器 类型要么是有状态的,要么是无状态的。通常,有状态的分配器类型可以具有不等的值,这些值表示不同的内存资源,而无状态的分配器类型表示单个内存资源。
虽然自定义分配器不需要是无状态的,但标准库中是否有状态分配器的使用以及如何使用是实现定义的。如果实现不支持此类使用,则使用不相等的分配器值可能会导致实现定义的运行时错误或未定义的行为。 |
(直到 C++11) |
自定义分配器可能包含状态。每个容器或其他分配器感知对象都存储所提供分配器的实例,并通过 std::allocator_traits 控制分配器的替换。 |
(自 C++11 起) |
无状态分配器类型的实例始终比较相等。无状态分配器类型通常实现为空类,适用于 空基类优化。
std::allocator_traits 的成员类型 |
(自 C++11 起) |
[编辑] 奇特指针
当成员类型 pointer
不是原始指针类型时,它通常被称为 "奇特指针"。此类指针是为了支持分段内存体系结构而引入的,如今用于访问分配在与原始指针访问的同构虚拟地址空间不同的地址空间中的对象。奇特指针的一个例子是映射地址无关指针 boost::interprocess::offset_ptr
,它使得在共享内存和内存映射文件中分配基于节点的数据结构(如 std::set)成为可能,这些文件在每个进程中映射到不同的地址。奇特指针可以独立于提供它们的分配器使用,通过类模板 std::pointer_traits(自 C++11 起)。 可以使用函数 std::to_address 从奇特指针获取原始指针。(自 C++20 起)
标准库中对奇特指针和自定义大小/不同类型的使用是条件支持的。实现可能要求成员类型 |
(直到 C++11) |
概念为了定义查询对象 std::get_allocator,定义了以下仅供说明的概念。
仅供说明的概念 /*simple-allocator*/ 定义了 分配器 要求的最小可用性约束。 |
(自 C++26 起) |
[编辑] 标准库
以下标准库组件满足 分配器 要求
默认分配器 (类模板) | |
(C++11) |
实现多级容器的多级分配器 (类模板) |
(C++17) |
一个基于其构造的 std::pmr::memory_resource 支持运行时多态的分配器 (类模板) |
[编辑] 示例
展示了一个 C++11 分配器,除了 [[nodiscard]]
添加到匹配 C++20 样式。
#include <cstdlib> #include <iostream> #include <limits> #include <new> #include <vector> template<class T> struct Mallocator { typedef T value_type; Mallocator() = default; template<class U> constexpr Mallocator(const Mallocator <U>&) noexcept {} [[nodiscard]] T* allocate(std::size_t n) { if (n > std::numeric_limits<std::size_t>::max() / sizeof(T)) throw std::bad_array_new_length(); if (auto p = static_cast<T*>(std::malloc(n * sizeof(T)))) { report(p, n); return p; } throw std::bad_alloc(); } void deallocate(T* p, std::size_t n) noexcept { report(p, n, 0); std::free(p); } private: void report(T* p, std::size_t n, bool alloc = true) const { std::cout << (alloc ? "Alloc: " : "Dealloc: ") << sizeof(T) * n << " bytes at " << std::hex << std::showbase << reinterpret_cast<void*>(p) << std::dec << '\n'; } }; template<class T, class U> bool operator==(const Mallocator <T>&, const Mallocator <U>&) { return true; } template<class T, class U> bool operator!=(const Mallocator <T>&, const Mallocator <U>&) { return false; } int main() { std::vector<int, Mallocator<int>> v(8); v.push_back(42); }
可能的输出
Alloc: 32 bytes at 0x2020c20 Alloc: 64 bytes at 0x2023c60 Dealloc: 32 bytes at 0x2020c20 Dealloc: 64 bytes at 0x2023c60
[编辑] 缺陷报告
以下更改行为的缺陷报告被追溯应用到之前发布的 C++ 标准。
DR | 应用于 | 已发布的行为 | 正确行为 |
---|---|---|---|
LWG 179 | C++98 | pointer 和 const_pointer 不需要彼此可比较 |
需要 |
LWG 199 | C++98 | a.allocate(0) 的返回值尚不清楚 | 未指定 |
LWG 258 (N2436) |
C++98 | 分配器之间的相等关系不 需要是自反的、对称的或传递的 |
需要是自反的、 对称的和传递的 |
LWG 274 | C++98 | T 可以是限定为常量的类型或引用类型,这可能会导致 std::allocator 不良定义[1] |
禁止这些类型 |
LWG 2016 | C++11 | 分配器的复制、移动和交换操作在 使用时可能会抛出异常 |
要求为非抛出异常 |
LWG 2081 | C++98 C++11 |
分配器不需要支持复制 赋值(C++98)和移动赋值(C++11) |
需要 |
LWG 2108 | C++11 | 没有办法表明分配器是无状态的 | 提供 is_always_equal |
LWG 2263 | C++11 | 对 LWG 问题 179 的解决在 C++11 中意外被删除了 并且没有泛化到 void_pointer 和 const_void_pointer |
已恢复并泛化 |
LWG 2447 | C++11 | T 可以是限定为易变的类型 |
禁止这些类型 |
LWG 2593 | C++11 | 从分配器移动可能会修改其值 | 禁止修改 |
P0593R6 | C++98 | allocate 不需要为其分配的存储空间创建数组对象。分配存储空间。 |
需要 |
- ↑ std::allocator 的成员类型
reference
和const_reference
分别定义为T&
和const T&
。