C++ 具名要求:分配器 (Allocator)
封装访问/寻址,分配/解分配,以及对象的构造/析构的策略。
可能需要分配或释放内存的每个标准库组件,从 std::string、std::vector 和除 std::array 以外的所有容器,到 std::shared_ptr 和 std::function,都通过分配器 (Allocator) 进行这些操作:分配器是满足下列要求的类类型对象。
许多分配器要求的实现是可选的,因为所有知分配器类,包括标准库容器,都通过 std::allocator_traits 访问分配器,而 std::allocator_traits 提供这些要求的默认实现。
要求
给定
-  
T,无 const 限定的非引用类型 (C++11 前)无 cv 限定的对象类型 (C++11 起) -  
A,T类型的分配器 (Allocator) 类型 -  a,
A类型对象 -  
B,某个无 cv 限定的对象类型U的对应分配器 (Allocator) 类型(由重绑定A获得) -  b,
B类型对象 - p,std::allocator_traits<A>::pointer 类型值,由调用 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_last 请求和返回的元素数之间(可以等于任一边界) (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 (可选)  | 
  | 
注:
- ↑ 参阅后述缀饰指针。
 - ↑ 
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 起) | 
有状态与无状态分配器
每个分配器 (Allocator) 类型要么是有状态要么是无状态的。通常来说,有状态分配器类型能拥有代表有别的内存资源的不相等值,而无状态分配器类型代表单一内存资源。
| 
 尽管不要求自定义分配器为无状态,标准库中是否及如何支持分配器由实现定义。如果实现不支持使用不相等的分配器值,那么这种使用可能导致实现定义的运行时错误或未定义行为。  | 
(C++11 前) | 
| 
 定制分配器可含有状态。每个容器或另一知分配器对象存储提供的分配器的实例并通过 std::allocator_traits 控制分配器替换。  | 
(C++11 起) | 
无状态分配器类型的实例始终比较相等。无状态分配器类型常实现为空类并适合空基类优化。
| 
 std::allocator_traits 的成员类型   | 
(C++17 起) | 
缀饰指针
当成员类型 pointer 不是原生指针时,它通常被称为“缀饰指针(fancy pointer)”。这种指针曾为支持分段内存架构而引入,并在当今用于访问在某些不同于原生指针所访问的同质虚拟地址空间的地址空间中所分配的对象。缀饰指针的一个实例是映射的不依赖地址指针 boost::interprocess::offset_ptr,它使得在共享内存和在每个进程中映射到不同地址的映射到内存文件中,分配 std::set 一类的基于结点的数据结构可行。通过类模板 std::pointer_traits, (C++11 起)可以独立于提供缀饰指针的分配器而使用它们。能用函数 std::to_address 从缀饰指针获得裸指针。 (C++20 起)
| 
 在标准库中使用缀饰指针和定制的大小/差类型是条件性支持的。实现可以要求成员类型   | 
(C++11 前) | 
标准库
下列标准库组件满足分配器 (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_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 << "在 " << std::hex << std::showbase << reinterpret_cast<void*>(p) << std::dec << (alloc ? " 分配 " : " 解分配 ") << sizeof(T) * n << " 个字节\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); }
可能的输出:
在 0x2020c20 分配 32 个字节 在 0x2023c60 分配 64 个字节 在 0x2020c20 解分配 32 个字节 在 0x2023c60 解分配 64 个字节
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 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&。