C++ 命名需求: 分配器 (Allocator)
封装了对象的访问/寻址、分配/释放和构造/析构的策略。
每一个可能需要分配或释放存储的标准库组件,从 std::string、std::vector 以及所有容器,除了 std::array(自 C++11 起) 和 std::inplace_vector(自 C++26 起),到 std::shared_ptr 和 std::function(直到 C++17),都通过一个 分配器 (Allocator) 来完成:一个满足以下要求的类类型对象。
许多分配器要求的实现是可选的,因为所有 分配器感知容器 (AllocatorAwareContainer) 都通过 std::allocator_traits 间接访问分配器,而 std::allocator_traits 提供了这些要求的默认实现。
目录 |
[编辑] 要求
给定
-
T
,一个非 const、非引用类型(直到 C++11)非 const 对象类型(自 C++11 起)(直到 C++17) cv 不合格对象类型(自 C++17 起), -
A
,类型T
的分配器 (Allocator) 类型, - a,类型
A
的对象, -
B
,对应于某个 cv 不合格对象类型U
的分配器 (Allocator) 类型(通过重新绑定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 的值。
类型 ID | 别名类型 | 要求 |
---|---|---|
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 的存储,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...) (可选) | (未使用) | 在先前分配的存储中,在 xp 指向的地址处,使用 args... 作为构造函数参数,构造一个类型为 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) 的先前值。 |
|
类型 ID | 别名类型 | 要求 |
A::is_always_equal (可选) |
std::true_type 或 std::false_type 或派生自此类。 |
|
表达式 | 返回类型 | 描述 |
---|---|---|
a.select_on_container_copy_construction() (可选) |
A
|
|
类型 ID | 别名类型 | 描述 |
A::propagate_on_container_copy_assignment (可选) |
std::true_type 或 std::false_type 或派生自此类。 |
|
A::propagate_on_container_move_assignment (可选) |
| |
A::propagate_on_container_swap (可选) |
|
注意
- ↑ 另请参阅下面的 花式指针。
- ↑
rebind
仅在当此分配器是SomeAllocator<T, Args>
形式的模板时才可选(由 std::allocator_traits 提供),其中Args
是零个或多个额外的模板类型参数。
给定
- x1 和 x2,类型为
X::void_pointer
、X::const_void_pointer
、X::pointer
或X::const_pointer
的对象(可能不同)
- 那么,x1 和 x2 是等价值的指针值,当且仅当 x1 和 x2 都可以通过仅使用这四种类型的 static_cast 序列显式转换为类型
X::const_pointer
的两个相应对象 px1 和 px2,并且表达式 px1 == px2 求值为 true。
给定
- 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
的等价值对象替换,语义不变。
上述要求使得比较 容器 (Container) 的 iterator
和 const_iterator
成为可能。
分配器完整性要求对于类型
|
(C++17 起) |
[编辑] 有状态和无状态分配器
每个分配器 (Allocator) 类型要么是有状态的,要么是无状态的。通常,有状态分配器类型可以有不相等的值,表示不同的内存资源,而无状态分配器类型表示单个内存资源。
尽管自定义分配器不要求是无状态的,但在标准库中使用有状态分配器的方式和是否支持是实现定义的。如果实现不支持此类用法,使用不相等的分配器值可能导致实现定义的运行时错误或未定义行为。 |
(C++11 前) |
自定义分配器可以包含状态。每个容器或其他分配器感知对象都存储一个提供的分配器实例,并通过 std::allocator_traits 控制分配器替换。 |
(C++11 起) |
无状态分配器类型的实例总是比较相等。无状态分配器类型通常实现为空类,适用于 空基类优化。
std::allocator_traits 的成员类型 |
(C++11 起) |
[编辑] 花式指针 (Fancy pointers)
当成员类型 pointer
不是原始指针类型时,它通常被称为 “花式指针”。引入此类指针是为了支持分段内存架构,今天它们用于访问与原始指针访问的同构虚拟地址空间不同的地址空间中分配的对象。花式指针的一个示例是映射地址无关指针 boost::interprocess::offset_ptr
,它使得在共享内存和内存映射文件中分配基于节点的_数据结构(如 std::set)成为可能,这些文件在每个进程中映射到不同的地址。花式指针可以独立于提供它们的分配器使用,通过类模板 std::pointer_traits(自 C++11 起)。 函数 std::to_address 可用于从花式指针获取原始指针。(自 C++20 起)
在标准库中使用花式指针和自定义大小/不同类型是条件支持的。实现可能要求成员类型 |
(C++11 前) |
概念对于查询对象 std::get_allocator 的定义,定义了以下仅用于阐释的概念。
仅用于阐释的概念 /*simple-allocator*/ 定义了分配器 (Allocator) 需求的最小可用性约束。 |
(C++26 起) |
[编辑] 标准库
以下标准库组件满足分配器 (Allocator) 要求
默认分配器 (类模板) | |
(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++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
LWG 179 | C++98 | pointer 和 const_pointer 不需要彼此可比较不需要彼此可比较 |
需要 |
LWG 199 | C++98 | a.allocate(0) 的返回值不明确 | 未指定 |
LWG 258 (N2436) |
C++98 | 分配器之间的相等关系不需要是自反、对称或传递的 不需要是自反、对称或传递的 |
要求是自反的, 对称和传递的 |
LWG 274 | C++98 | T 可以是 const 限定类型或引用类型,使得 std::allocator 可能格式错误[1] |
禁止这些类型 |
LWG 2016 | C++11 | 分配器的复制、移动和交换操作在使用时可能抛出异常 分配器在使用时可能抛出异常 |
要求不抛出 |
LWG 2081 | C++98 C++11 |
分配器不需要支持复制赋值 (C++98) 和移动赋值 (C++11) 分配器不需要支持复制赋值 (C++98) 和移动赋值 (C++11) |
需要 |
LWG 2108 | C++11 | 无法表示分配器是无状态的 | 提供 is_always_equal |
LWG 2263 | C++11 | LWG issue 179 的解决方案在 C++11 中意外丢失 并且未推广到 void_pointer 和 const_void_pointer |
恢复并推广 |
LWG 2447 | C++11 | T 可以是 volatile 限定对象类型 |
禁止这些类型 |
LWG 2593 | C++11 | 从分配器移动可能修改其值 | 禁止修改 |
P0593R6 | C++98 | allocate 不需要在其分配的存储中创建数组对象不需要在其分配的存储中创建数组对象 |
需要 |
- ↑ std::allocator 的成员类型
reference
和const_reference
分别定义为T&
和const T&
。