C++ 具名要求:分配器 (Allocator)
封装访问/寻址,分配/解分配,以及对象的构造/析构的策略。
可能需要分配或释放内存的每个标准库组件,从 std::string
、std::vector
和 std::array
以外的所有容器,到 std::shared_ptr
和 std::function
,都通过分配器 (Allocator) 进行这些操作:分配器是满足下列要求的类类型对象。
许多分配器要求的实现是可选的,因为所有知分配器类,包括标准库容器,都通过 std::allocator_traits
访问分配器,而 std::allocator_traits 提供这些要求的默认实现。
要求
给定
-
T
,无 cv 限定的对象类型 -
A
,T
类型的分配器 (Allocator) 类型 -
a
,A
类型对象 -
B
,某无 cv 限定的对象类型U
的对应分配器 (Allocator) 类型(由重绑定A
获得) -
b
,B
类型对象 -
p
,allocator_traits<A>::pointer
类型值,由调用 allocator_traits<A>::allocate() 获得 -
cp
,allocator_traits<A>::const_pointer
类型值,由从p
转换获得 -
vp
,allocator_traits<A>::void_pointer
类型值,由从p
转换获得 -
cvp
,allocator_traits<A>::const_void_pointer
类型值,由从cp
或从vp
转换获得 -
xp
,指向某无 cv 限定类型X
的可解引用的指针 -
r
,由表达式 *p 获得的T
类型左值 -
n
,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] 类型数组对象的存储并创建该数组,但不构造数组元素。可以抛异常。 |
a.allocate(n, cvp) (可选) | 同 a.allocate(n) ,但可能以未指定的方式使用 cvp ( nullptr 或从 a.allocate() 获得的指针)以辅助局部性。
| |
a.deallocate(p, n) | (不使用) | 解分配 p 所指向的存储,该值必须由之前调用 allocate 返回且未被中间对 deallocate 的调用非法化。 n 必须匹配先前传给 allocate 的值。不抛异常。
|
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 (可选) (C++17 起) |
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 (可选) |
|
注:
- ↑ 参阅后述缀饰指针。
- ↑
rebind
仅若分配器是形式为 SomeAllocator<T, Args> 的模板,其中Args
是零或更多个额外的类型模板形参才为可选(由 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 起) |
缀饰指针
当成员类型 pointer
不是原生指针时,它通常被称为“缀饰指针(fancy pointer)”。这种指针曾为支持分段内存架构而引入,并在当今用于访问在某些不同于原生指针所访问的同质虚拟地址空间的地址空间中所分配的对象。缀饰指针的一个实例是映射的不依赖地址指针 boost::interprocess::offset_ptr,它使得在共享内存和在每个进程中映射到不同地址的映射到内存文件中,分配 std::set 一类的基于结点的数据结构可行。通过类模板 std::pointer_traits , (C++11 起)缀饰指针可以独立于提供它们的分配器而使用。能用函数 std::to_address 从缀饰指针获得裸指针。 (C++20 起)
标准库
下列标准库组件满足分配器 (Allocator) 要求:
默认的分配器 (类模板) | |
(C++11) |
为多级容器实现的多级分配器 (类模板) |
(C++17) |
以 std::memory_resource 构造,支持基于它的运行时多态的分配器 (类模板) |
示例
一个 C++11 分配器,但添加了 [[nodiscard]] 以符合 C++20 风格。
#include <cstdlib> #include <new> #include <limits> #include <iostream> #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_alloc(); 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 可相互比较
|
已要求 |
P0593R6 | C++98 | 未要求 allocate 在其所分配的存储中创建数组
|
已要求 |
LWG 2016 | C++11 | 分配器的复制、移动与交换操作在使用时可能抛出 | 要求不抛出 |
LWG 2263 | C++11 | LWG179 的解决方案在 C++11 中意外丢失 且未被推广到 void_pointer 与 const_void_pointer
|
恢复并推广 |
LWG 2593 | C++11 | 从分配器移动可能修改其值 | 禁止修改 |