C++ 具名要求:分配器 (Allocator)
封装访问/寻址,分配/解分配,以及对象的构造/析构的策略。
可能需要分配或释放内存的每个标准库组件,从 std::string、std::vector 和除 std::array 与 std::inplace_vector (C++26 起)以外的(C++11 起)所有容器,到 std::shared_ptr 和 std::function(C++17 前),都通过分配器 (Allocator) 进行这些操作:分配器是满足下列要求的类类型对象。
许多分配器要求的实现是可选的,因为所有知分配器容器 (AllocatorAwareContainer) 都通过 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(可选) |
|
注:
给定
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 起) |
无状态分配器类型的实例始终比较相等。无状态分配器类型常实现为空类并适合空基类优化。
|
std::allocator_traits 的成员类型 |
(C++11 起) |
缀饰指针
当成员类型 pointer 不是原生指针时,它通常被称为“缀饰指针(fancy pointer)”。这种指针曾为支持分段内存架构而引入,并在当今用于访问在某些不同于原生指针所访问的同质虚拟地址空间的地址空间中所分配的对象。缀饰指针的一个实例是映射的不依赖地址指针 boost::interprocess::offset_ptr,它使得在每个进程中映射到不同地址的共享内存和内存映射文件中分配 std::set 一类的基于结点的数据结构变得可行。通过类模板 std::pointer_traits,(C++11 起)可以独立于提供缀饰指针的分配器而使用它们。能用函数 std::to_address 从缀饰指针获得裸指针。(C++20 起)
|
在标准库中使用缀饰指针和定制的大小/差类型是条件性支持的。实现可以要求成员类型 |
(C++11 前) |
概念为了定义查询对象 std::get_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 << "在 " << 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 在其所分配的存储中创建数组
|
已要求 |