C++ 具名要求:分配器 (Allocator)

来自cppreference.com
< cpp‎ | named req
 
 
C++ 具名要求
基础
类型属性
库所属
容器
容器元素
迭代器
流 I/O
随机数
并发
(C++11)
(C++11)
其他
 

封装访问/寻址,分配/解分配,以及对象的构造/析构的策略。

可能需要分配或释放内存的每个标准库组件,从 std::stringstd::vector 和除 std::array 以外的所有容器,到 std::shared_ptrstd::function,都通过分配器 (Allocator) 进行这些操作:分配器是满足下列要求的类类型对象。

许多分配器要求的实现是可选的,因为所有知分配器类,包括标准库容器,都通过 std::allocator_traits 访问分配器,而 std::allocator_traits 提供这些要求的默认实现。

要求

给定

  • T ,无 cv 限定的对象类型
  • AT 类型的分配器 (Allocator) 类型
  • aA 类型对象
  • B ,某无 cv 限定的对象类型 U 的对应分配器 (Allocator) 类型(由重绑定 A 获得)
  • bB 类型对象
  • pallocator_traits<A>::pointer 类型值,由调用 allocator_traits<A>::allocate() 获得
  • cpallocator_traits<A>::const_pointer 类型值,由从 p 转换获得
  • vpallocator_traits<A>::void_pointer 类型值,由从 p 转换获得
  • cvpallocator_traits<A>::const_void_pointer 类型值,由从 cp 或从 vp 转换获得
  • xp ,指向某无 cv 限定类型 X 的可解引用的指针
  • r ,由表达式 *p 获得的 T 类型左值
  • nallocator_traits<A>::size_type 类型值
内部类型
类型标识 别名使用的类型 要求
A::pointer (可选) (未指明)[1]
A::const_pointer (可选) (未指明)
A::void_pointer (可选) (未指明)
A::const_void_pointer (可选) (未指明)
  • 满足可空指针 (NullablePointer)
  • A::pointerA::const_pointerA::void_pointer 可转换为 A::const_void_pointer
  • B::const_void_pointerA::const_void_pointer 为同一类型。
A::value_type T
A::size_type (可选) (未指明)
  • 无符号整数类型。
  • 能表示 A 所能分配的最大对象的大小。
A::difference_type (可选) (未指明)
  • 有符号整数类型。
  • 能表示任何二个指向 A 所分配的对象的指针的差
A::template rebind<U>::other
(可选)[2]
B
  • 对于任何 UB::template rebind<T>::otherA
指针上的操作
表达式 返回类型 要求
*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) ,但可能以未指定的方式使用 cvpnullptr 或从 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 解分配才为 true
  • 建立自反、对称和传递关系。
  • 不抛异常。
a1 != a2
  • !(a1==a2)
声明 效果 要求
A a1(a) 复制构造 a1 使得 a1 == a
(注:每个分配器 (Allocator) 亦满足可复制构造 (CopyConstructible) 。)
  • 不抛异常。
A a1 = a
A a(b) 构造 a 使得 B(a)==bA(b)==a
(这隐含所有由 rebind 联系的分配器均维护彼此的资源,例如内存池。)
  • 不抛异常。
A a1(std::move(a)) 构造 a1 使得它等于先前 a 的值。
  • 不抛异常。
  • 不更改 a 的值且 a1 == a
A a1 = std::move(a)
A a(std::move(b)) 构造 a 使得它等于先前 A(b) 的值。
  • 不抛异常。
类型标识 别名使用的类型 要求
A::is_always_equal
(可选) (C++17 起)
std::true_typestd::false_type 或从它们派生。
容器操作上的影响
表达式 返回类型 描述
a.select_on_container_copy_construction()
(可选)
A
  • 提供从当前使用 a 的容器复制构造容器所用的 A 的实例。
  • (通常返回 a 的副本或默认构造的 A 。)
类型标识 别名使用的类型 描述
A::propagate_on_container_copy_assignment
(可选)
std::true_typestd::false_type 或从它们派生。
  • 若复制赋值使用 A 类型分配器的容器时需要复制它则为 std::true_type 或其派生类。
  • 若此成员为 std::true_type 或从它派生,则 A 必须满足可复制赋值 (CopyAssignable) 且复制操作必须不抛异常。
  • 注意若源与目标容器的分配器不比较相等,则复制赋值必须用旧分配器解分配目标的内存,然后在复制元素(和分配器)前用新分配器分配内存。
A::propagate_on_container_move_assignment
(可选)
  • 若移动赋值使用 A 类型分配器的容器时需要移动它则为 std::true_type 或其派生类。
  • 若此成员为 std::true_type 或从它派生,则 A 必须满足可移动赋值 (MoveAssignable) 且移动操作必须不抛异常。
  • 若不提供此成员或它从 std::false_type 派生,而源与目标容器的分配器不比较相等,则移动赋值不能取得源内存的所有权,并且必须单独地复制赋值或复制构造元素,按需重置其自身内存的大小。
A::propagate_on_container_swap
(可选)
  • 若交换二个使用 A 类型分配器的容器时需要移动它们则为 std::true_type 或其派生类。
  • 若此成员为 std::true_type 或从它派生,则 A 的左值必须为可交换 (Swappable) 且交换操作必须不抛异常。
  • 若不提供此成员或它从 std::false_type 派生,且二个容器的分配器不比较相等,则容器交换的行为未定义。

注:

  1. 参阅后述缀饰指针
  2. rebind 仅若分配器是形式为 SomeAllocator<T, Args> 的模板,其中 Args 是零或更多个额外的类型模板形参才为可选(由 std::allocator_traits 提供)。

给定

  • x1x2 ,(可能不同)类型 X::void_pointerX::const_void_pointerX::pointerX::const_pointer 的对象。

x1x2等价值的指针值,当且仅当 x1x2 能用 static_cast ,仅使用这四个类型的序列显式转换成二个对应的 X::const_pointer 类型对象 px1px2 ,而表达式 px1 == px2 求值为 true

给定

  • w1w2X::void_pointer 类型对象。

则对于表达式 w1 == w2w1 != w2 ,可将一个或两个对象替换成等价值X::const_void_pointer 类型对象而无语义更改。

给定

  • p1p2X::pointer 类型对象

则对于表达式 p1 == p2p1 != p2p1 < p2p1 <= p2p1 >= p2p1 > p2p1 - p2 ,可将一个或两个对象替换成等价值X::const_pointer 类型对象而无语义更改。

以上要求使得能比较容器 (Container) iteratorconst_iterator

分配器完整性要求

若无论 T 是否为完整类型下列二者皆为真,则针对类型 T 的分配器类型 X 还额外满足分配器完整性要求

  • X 是完整类型
  • value_type 之外,所有 std::allocator_traits<X> 的成员都是完整类型。
(C++17 起)

有状态与无状态分配器

每个分配器 (Allocator) 类型要么是有状态要么是无状态。通常来说,有状态分配器类型能拥有代表有别的内存资源的不相等值,而无状态分配器类型代表单一内存资源。

尽管不要求自定义分配器为无状态,标准库中是否及如何支持分配器是实现定义的。若实现不支持使用不相等的分配器值,则这种使用可能导致实现定义的运行时错误或未定义行为。

(C++11 前)

定制分配器可含有状态。每个容器或另一知分配器对象存储提供的分配器的实例并通过 std::allocator_traits 控制分配器替换。

(C++11 起)

无状态分配器类型的实例始终比较相等。无状态分配器类型常实现为空类并适合空基类优化

std::allocator_traits 的成员类型 is_always_equal 有意用于确定分配器类型是否为无状态。

(C++17 起)

缀饰指针

当成员类型 pointer 不是原生指针时,它通常被称为“缀饰指针(fancy pointer)”。这种指针曾为支持分段内存架构而引入,并在当今用于访问在某些不同于原生指针所访问的同质虚拟地址空间的地址空间中所分配的对象。缀饰指针的一个实例是映射的不依赖地址指针 boost::interprocess::offset_ptr,它使得在共享内存和在每个进程中映射到不同地址的映射到内存文件中,分配 std::set 一类的基于结点的数据结构可行。通过类模板 std::pointer_traits (C++11 起)可以独立于提供缀饰指针的分配器而使用它们。能用函数 std::to_address 从缀饰指针获得裸指针。 (C++20 起)

在标准库中使用缀饰指针和定制的大小/差类型是条件性支持的。实现可以要求成员类型 pointerconst_pointersize_typedifference_type 分别为 value_type*const value_type*std::size_tstd::ptrdiff_t

(C++11 前)

标准库

下列标准库组件满足分配器 (Allocator) 要求:

默认的分配器
(类模板)
为多级容器实现的多级分配器
(类模板)
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 << (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 未要求 pointerconst_pointer 可相互比较 已要求
P0593R6 C++98 未要求 allocate 在其所分配的存储中创建数组 已要求
LWG 2016 C++11 分配器的复制、移动与交换操作在使用时可能抛出 要求不抛出
LWG 2263 C++11 LWG179 的解决方案在 C++11 中意外丢失
且未被推广到 void_pointerconst_void_pointer
恢复并推广
LWG 2593 C++11 从分配器移动可能修改其值 禁止修改