C++ 参考手册

位置:首页 > C++ 参考手册 >具名要求 > C++ 具名要求: 分配器 (Allocator)

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

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

某些要求是可选的:模板 std::allocator_traits 为所有可选要求提供默认实现,且所有标准库容器和其他具分配器类,都是通过 std::allocator_traits 访问分配器,而并不直接访问。

要求

给定

  • 无 cv 限定的对象类型 T
  • 针对类型 T分配器 (Allocator) 类型 A
  • A 类型的对象 a
  • B,为针对某个无 cv 限定的对象类型 U 的对应分配器 (Allocator) 类型(重绑定 A 所得)
  • B 类型的对象 b
  • allocator_traits<A>::pointer 类型的值 ptr,由调用 allocator_traits<A>::allocate() 所获得
  • allocator_traits<A>::const_pointer 类型的值 cptr,从 ptr 转换而获得
  • allocator_traits<A>::void_pointer 类型的值 vptr,从 ptr 转换而获得
  • allocator_traits<A>::const_void_pointer 类型的值 cvptr,从 cptrvptr 转换而获得
  • xptr,为针对某各无 cv 限定对象类型 X 的可解引用指针
  • r,为从表达式 *ptr 获得的 T 类型的左值
  • allocator_traits<A>::size_type 类型的值 n
表达式 要求 返回类型
A::pointer (可选) 满足可空指针 (NullablePointer) 遗留随机访问迭代器 (LegacyRandomAccessIterator) 遗留连续迭代器 (LegacyContiguousIterator) (见下文缀饰指针
A::const_pointer (可选) A::pointer 可转换为 A::const_pointer可空指针 (NullablePointer) 遗留随机访问迭代器 (LegacyRandomAccessIterator) 遗留连续迭代器 (LegacyContiguousIterator)
A::void_pointer (可选) A::pointer 可转换为 A::void_pointer

B::void_pointerA::void_pointer 是同一类型。满足可空指针 (NullablePointer)

A::const_void_pointer (可选) A::pointerA::const_pointerA::void_pointer 可转换到 A::const_void_pointer

B::const_void_pointerA::const_void_pointer 是同一类型。满足可空指针 (NullablePointer)

A::value_type 类型 T
A::size_type (可选) A::size_type 能表示 A 所能分配的最大对象的大小 无符号整数类型
A::difference_type (可选) A::difference_type 能表示任意两个指向 A 所分配对象的指针之间的差 有符号整数类型
A::template rebind<U>::other (可选)[1] 对于任何 UB::template rebind<T>::otherA 类型 B
*ptr T&
*cptr *cptr*ptr 标识同一对象 const T&
ptr->m (*ptr).m 良构,则与 (*ptr).m 相同 T::m 的类型
cptr->m (*cptr).m 良构,则与 (*cptr).m 相同 T::m 的类型
static_cast<A::pointer>(vptr) static_cast<A::pointer>(vptr) == ptr A::pointer
static_cast<A::const_pointer>(cvptr) static_cast<A::const_pointer>(cvptr) == cptr A::const_pointer
std::pointer_traits<A::pointer>::pointer_to(r) A::pointer
a.allocate(n) 分配适合 nT 类型对象的存储,但不构造它们。可以抛异常。 A::pointer
a.allocate(n, cvptr) (可选) a.allocate(n) 相同,但可以以未指明的方式使用 cvptr(从 a.allocate() 获得的指针或 nullptr_t)以协助局部性 A::pointer
a.deallocate(ptr, n) 解分配 ptr 所指向的存储,该指针必须是先前从对 allocate 的调用获得的指针,且未因中间对 deallocate 的调用而失效。n 必须匹配先前传递给 allocate 的值。不抛异常。 (不使用)
a.max_size() (可选) 能传递给 A::allocate() 的最大值 A::size_type
a1 == a2 仅若分配器 a1 分配的存储能通过 a2 解分配才返回 true。建立自反、对称和可传递关系。不抛异常。 bool
a1 != a2 !(a1==a2) 相同 bool
A a1(a)

A a1 = a

复制构造 a1 以满足 a1 == a。不抛异常。(注意:所有分配器 (Allocator) 也都满足可复制构造 (CopyConstructible)
A a(b) 构造满足 B(a)==bA(b)==aa。不抛异常。(注意,这意味着所有因 rebind 相关联的分配器都彼此维护资源,例如内存池)
A a1(std::move(a))

A a1 = std::move(a)

构造 a1 使之等于 a 之前的值。不抛异常。 a 的值不变且 a1 == a (C++17 起)
A a(std::move(b)) 构造 a 使之等于 A(b) 之前的值。不抛异常。
a.construct(xptr, args) (可选) 在先前分配的存储中由 xptr 所指向的地址上,以 args 为构造函数实参构造一个 X 类型的对象
a.destroy(xptr) (可选) 析构 xptr 所指向的 X 类型的对象,但不解分配任何存储。
a.select_on_container_copy_construction() (可选) 提供一个 A 实例,以被从当前正使用 a 的容器所复制构造的容器使用。通常要么返回 a 的副本,要么为默认构造的 A() A
A::propagate_on_container_copy_assignment (可选) 若在使用 A 类型的分配器的容器被复制赋值时需要复制该分配器,则为 true。注意,若源和目标容器的分配器比较不相等,则复制赋值时必须用旧分配器解分配目标的内存,再在进行各元素(和分配器)的复制之前用新分配器分配内存 std::true_typestd::false_type 或从它派生的类型
A::propagate_on_container_move_assignment (可选) 若在使用 A 类型的分配器的容器被移动赋值时需要移动该分配器,则为 true。若此成员是 false 且源和目标容器的分配器比较不相等,则移动赋值不能夺取源内存的所有权,而必须逐个进行元素的移动赋值或移动构造,按需调整其自身内存的大小。 std::true_typestd::false_type 或从它派生的类型
A::propagate_on_container_swap (可选) 若在使用 A 类型的分配器的两个容器进行交换时需要交换分配器,则为 true。若此成员是 false 且两个容器的分配器比较不相等,则容器交换的行为未定义。 std::true_typestd::false_type 或从它派生的类型
A::is_always_equal (C++17 起) (可选) 若两个 A 类型的分配器始终比较相等则为 true。若不提供,则 std::allocator_traits 默认它等于 std::is_empty<A>::type std::true_typestd::false_type 或从它派生的类型

注:

  1. 仅当此分配器是 SomeAllocator<T, Args> 形式的模板(其中 Args 是零或更多额外的模板类型形参)时,rebind 才是可选的(由 std::allocator_traits 提供)。

另外,为使类型 A 满足分配器 (Allocator)

(C++17 起)

给定

  • X::void_pointerX::const_void_pointerX::pointerX::const_pointer 类型的对象 x1x2,类型可以不同

则,当且仅当 x1x2 分别都能用只使用这四种类型的 static_casts 的序列显式转换为 X::const_pointer 类型的对象 px1px2,且表达式 px1 == px2 求值为 true,则 x1x2值等价的指针值

给定

  • X::void_pointer 类型的对象 w1w2

则对于表达式 w1 == w2w1 != w2,任一或两个对象都可由值等价X::const_void_pointer 类型的对象所替换,而语义没有变化。

给定

  • X::pointer 类型的对象 p1p2

则,对于表达式 p1 == p2p1 != p2p1 < p2p1 <= p2p1 >= p2p1 > p2p1 - p2},任一或两个对象都可由值等价X::const_pointer 类型的对象所替换,而语义没有变化。

上述要求使得比较容器 (Container) 的 iterator 和 const_iterator 成为可能。

(C++14 起)


分配器完整性要求

若无论 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>
 
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))))
      return p;
 
    throw std::bad_alloc();
  }
  void deallocate(T* p, std::size_t) noexcept { std::free(p); }
};
 
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; }