C++ 命名要求: Allocator
封装了对象的访问/寻址、分配/释放和构造/析构策略。
每个可能需要分配或释放存储的标准库组件,从 std::string、 std::vector 和每个容器,除了 std::array(自 C++11 起) 和 std::inplace_vector(自 C++26 起),到 std::shared_ptr 和 std::function(截至 C++17),都是通过 Allocator 来实现的: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 的值。
类型标识 | 别名类型 | 要求 |
---|---|---|
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 指向的地址构造类型为 X 的对象,使用 args... 作为构造函数参数。 |
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 是等价取值的指针值,当且仅当 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 起) |
无状态分配器类型的实例始终比较相等。无状态分配器类型通常实现为空类,并且适合 空基类优化。
成员类型 |
(自 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*/ 定义了 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++ 标准。
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 可以是 const 限定类型或引用类型,使得 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 可以是 volatile 限定的对象类型 |
禁止这些类型 |
LWG 2593 | C++11 | 从分配器移动可能会修改其值 | 禁止修改 |
P0593R6 | C++98 | allocate 不要求在它分配的存储中创建数组对象 |
要求 |
- ↑ std::allocator 的成员类型
reference
和const_reference
分别定义为T&
和const T&
。