C++ named requirements: Allocator
封装了对象的访问/寻址、分配/释放以及构造/销毁策略。
每个可能需要分配或释放存储空间的标准库组件,从 std::string 、 std::vector 及所有容器 (除 std::array 外 (C++11 起) 和 std::inplace_vector (C++26 起) ,到 std::shared_ptr 及 std::function (C++17 前) ,都是通过 分配器 来实现的:即满足下列要求的类类型对象。
许多分配器要求的实现是可选的,因为所有 AllocatorAwareContainer 都通过 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... ) (可选) | (未使用) |
在
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 ) 的先前值。 |
|
| 类型标识 | 别名类型 | 要求 |
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
(可选) |
|
注意事项:
- ↑ 另请参阅下文中的 fancy pointers 。
-
↑
rebind仅在分配器为SomeAllocator<T, Args>形式的模板时才是可选的(由 std::allocator_traits 提供),其中Args是零个或多个额外的模板类型参数。
给定
-
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的 等价值 对象替换,而语义保持不变。
上述要求使得比较
Container
的
iterator
与
const_iterator
成为可能。
分配器完整性要求
若满足以下两个条件,则类型
|
(C++17 起) |
有状态与无状态分配器
每个 Allocator 类型要么是 有状态的 ,要么是 无状态的 。通常来说,有状态的分配器类型可以具有不等值,这些值表示不同的内存资源;而无状态的分配器类型则表示单一的内存资源。
|
尽管自定义分配器不要求是无状态的,但在标准库中使用有状态分配器的情况及其具体方式由实现定义。若实现不支持此类用法,使用不相等分配器值可能导致实现定义的运行时错误或未定义行为。 |
(C++11 前) |
|
自定义分配器可包含状态。每个容器或其他感知分配器的对象存储所提供分配器的实例,并通过 std::allocator_traits 控制分配器替换。 |
(C++11 起) |
无状态分配器类型的实例总是比较相等。无状态分配器类型通常实现为空类,适用于 空基类优化 。
|
成员类型
|
(since C++11) |
Fancy pointers
当成员类型
pointer
不是原始指针类型时,通常被称为
"fancy pointer"
。这类指针的引入是为了支持分段式内存架构,如今被用于访问分配在非统一虚拟地址空间中的对象,这些地址空间无法通过原始指针直接访问。fancy pointer 的一个典型实例是映射地址无关指针
boost::interprocess::offset_ptr
,它使得在共享内存和内存映射文件中分配基于节点的数据结构(如
std::set
)成为可能,即使这些内存在不同进程中被映射到不同地址。fancy pointers 可以独立于提供它们的分配器使用
,通过类模板
std::pointer_traits
(C++11 起)
。
函数
std::to_address
可用于从 fancy pointer 获取原始指针。
(C++20 起)
|
标准库中对花式指针和自定义大小/不同类型的支持是有条件的。实现可能要求成员类型
|
(C++11 前) |
概念为定义查询对象 std::get_allocator ,定义了以下仅用于说明的概念。
仅用于说明的概念 /*simple-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) |
要求必须支持 |
| 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&。