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

< cpp‎ | named req
 
 
 

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

可能需要分配或释放内存的每个标准库组件,从 std::stringstd::vectorstd::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.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 解分配才为 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 (C++20 起)
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 起)

缀饰指针

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

标准库

下列标准库组件满足分配器 (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_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 未要求 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 从分配器移动可能修改其值 禁止修改