命名空间
变体
操作

C++ 命名需求: 分配器 (Allocator)

来自 cppreference.cn
 
 
C++ 命名要求
基本
类型属性
全库范围
分配器 (Allocator)




Container(容器)
容器元素
迭代器 (Iterator)
流 I/O
格式化器
随机数
并发
范围 (Ranges)
多维视图
其他

 

封装了对象的访问/寻址、分配/释放和构造/析构的策略。

每一个可能需要分配或释放存储的标准库组件,从 std::stringstd::vector 以及所有容器,除了 std::array(自 C++11 起)std::inplace_vector(自 C++26 起),到 std::shared_ptrstd::function(直到 C++17),都通过一个 分配器 (Allocator) 来完成:一个满足以下要求的类类型对象。

许多分配器要求的实现是可选的,因为所有 分配器感知容器 (AllocatorAwareContainer) 都通过 std::allocator_traits 间接访问分配器,而 std::allocator_traits 提供了这些要求的默认实现。

目录

[编辑] 要求

给定

  • T,一个非 const、非引用类型(直到 C++11)非 const 对象类型(自 C++11 起)(直到 C++17) cv 不合格对象类型(自 C++17 起)
  • A,类型 T分配器 (Allocator) 类型,
  • a,类型 A 的对象,
  • B,对应于某个 cv 不合格对象类型 U分配器 (Allocator) 类型(通过重新绑定 A 获得),
  • b,类型 B 的对象,
  • p,类型 std::allocator_traits<A>::pointer 的值,通过调用 std::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 的值。
内部类型
类型 ID 别名类型 要求
A::pointer (可选) (未指定)[1]
A::const_pointer (可选) (未指定)
A::void_pointer (可选) (未指定)
  • 满足 NullablePointer
  • A::pointer 可转换为 A::void_pointer
  • B::void_pointerA::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] 数组对象的存储并创建数组,但不构造数组元素。可能抛出异常。如果 n == 0,则返回值未指定。
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 的存储,p 必须是先前调用 allocate allocate_at_least(自 C++23 起) 返回的值,并且没有被中间的 deallocate 调用使无效。n 必须与先前传递给 allocate 的值匹配,或者在通过 allocate_at_least 请求和返回的元素数量之间(可以等于任一边界)(自 C++23 起)。不抛出异常。
a.max_size() (可选) A::size_type 可以传递给 A::allocate() 的最大值。
a.construct(xp, args...) (可选) (未使用) 在先前分配的存储中,在 xp 指向的地址处,使用 args... 作为构造函数参数,构造一个类型为 X 的对象。
a.destroy(xp) (可选) (未使用) 析构 xp 指向的类型 X 的对象,但不解除分配任何存储。
实例之间的关系
表达式 返回类型 要求
a1 == a2 bool
  • true 仅当分配器 a1 分配的存储可以通过 a2 解除分配时。
  • 建立自反、对称和传递关系。
  • 不抛出异常。
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) 的先前值。
  • 不抛出异常。
类型 ID 别名类型 要求
A::is_always_equal
(可选)
std::true_typestd::false_type 或派生自此类。
对容器操作的影响
表达式 返回类型 描述
a.select_on_container_copy_construction()
(可选)
A
  • 提供一个 A 实例,供从当前使用 a 的容器复制构造的容器使用。
  • (通常返回 a 的副本或默认构造的 A。)
类型 ID 别名类型 描述
A::propagate_on_container_copy_assignment
(可选)
std::true_typestd::false_type 或派生自此类。
  • std::true_type 或派生自它,如果在使用它的容器被复制赋值时,需要复制类型 A 的分配器。
  • 如果此成员是 std::true_type 或派生自它,则 A 必须满足 CopyAssignable,并且复制操作不能抛出异常。
  • 请注意,如果源容器和目标容器的分配器不比较相等,复制赋值必须使用旧的分配器解除分配目标容器的内存,然后使用新的分配器分配内存,再复制元素(和分配器)。
A::propagate_on_container_move_assignment
(可选)
  • std::true_type 或派生自它,如果在使用它的容器被移动赋值时,需要移动类型 A 的分配器。
  • 如果此成员是 std::true_type 或派生自它,则 A 必须满足 MoveAssignable,并且移动操作不能抛出异常。
  • 如果未提供此成员或派生自 std::false_type,并且源容器和目标容器的分配器不比较相等,则移动赋值不能获得源内存的所有权,必须单独移动赋值或移动构造元素,并根据需要调整自身内存大小。
A::propagate_on_container_swap
(可选)
  • std::true_type 或派生自它,如果当两个使用类型 A 分配器的容器交换时,需要交换这些分配器。
  • 如果此成员是 std::true_type 或派生自它,则类型 A 必须满足 Swappable,并且交换操作不能抛出异常。
  • 如果未提供此成员或派生自 std::false_type,并且两个容器的分配器不比较相等,则容器交换的行为未定义。

注意

  1. 另请参阅下面的 花式指针
  2. rebind 仅在当此分配器是 SomeAllocator<T, Args> 形式的模板时才可选(由 std::allocator_traits 提供),其中 Args 是零个或多个额外的模板类型参数。

给定

  • x1x2,类型为 X::void_pointerX::const_void_pointerX::pointerX::const_pointer 的对象(可能不同)
那么,x1x2等价值的指针值,当且仅当 x1x2 都可以通过仅使用这四种类型的 static_cast 序列显式转换为类型 X::const_pointer 的两个相应对象 px1px2,并且表达式 px1 == px2 求值为 true

给定

  • w1w2,类型为 X::void_pointer 的对象
那么,对于表达式 w1 == w2w1 != w2,一个或两个对象都可以被类型为 X::const_void_pointer等价值对象替换,语义不变。

给定

  • p1p2,类型为 X::pointer 的对象
那么,对于表达式 p1 == p2p1 != p2p1 < p2p1 <= p2p1 >= p2p1 > p2p1 - p2,一个或两个对象都可以被类型为 X::const_pointer等价值对象替换,语义不变。

上述要求使得比较 容器 (Container)iteratorconst_iterator 成为可能。

分配器完整性要求

对于类型 T 的分配器类型 X 额外满足分配器完整性要求,如果无论 T 是否是完整类型,以下两项都为真

  • X 是一个完整类型。
  • 除了 value_typestd::allocator_traits<X> 的所有成员类型都是完整类型。
(C++17 起)

[编辑] 有状态和无状态分配器

每个分配器 (Allocator) 类型要么是有状态的,要么是无状态的。通常,有状态分配器类型可以有不相等的值,表示不同的内存资源,而无状态分配器类型表示单个内存资源。

尽管自定义分配器不要求是无状态的,但在标准库中使用有状态分配器的方式和是否支持是实现定义的。如果实现不支持此类用法,使用不相等的分配器值可能导致实现定义的运行时错误或未定义行为。

(C++11 前)

自定义分配器可以包含状态。每个容器或其他分配器感知对象都存储一个提供的分配器实例,并通过 std::allocator_traits 控制分配器替换。

(C++11 起)

无状态分配器类型的实例总是比较相等。无状态分配器类型通常实现为空类,适用于 空基类优化

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

(C++11 起)

[编辑] 花式指针 (Fancy pointers)

当成员类型 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 前)

概念

对于查询对象 std::get_allocator 的定义,定义了以下仅用于阐释的概念。

template<class Alloc>

concept /*simple-allocator*/ = requires(Alloc alloc, std::size_t n)
{
    { *alloc.allocate(n) } -> std::same_as<typename Alloc::value_type&>;
    { alloc.deallocate(alloc.allocate(n), n) };  
} && std::copy_constructible<Alloc>

  && std::equality_comparable<Alloc>;

仅用于阐释的概念 /*simple-allocator*/ 定义了分配器 (Allocator) 需求的最小可用性约束。

(C++26 起)

[编辑] 标准库

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

默认分配器
(类模板) [编辑]
为多层容器实现多层分配器
(类模板) [编辑]
一个支持基于其构造的 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 << (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++ 标准。

缺陷报告 应用于 发布时的行为 正确的行为
LWG 179 C++98 pointerconst_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)
分配器不需要支持复制赋值 (C++98) 和移动赋值 (C++11)
需要
LWG 2108 C++11 无法表示分配器是无状态的 提供 is_always_equal
LWG 2263 C++11 LWG issue 179 的解决方案在 C++11 中意外丢失
并且未推广到 void_pointerconst_void_pointer
恢复并推广
LWG 2447 C++11 T 可以是 volatile 限定对象类型 禁止这些类型
LWG 2593 C++11 从分配器移动可能修改其值 禁止修改
P0593R6 C++98 allocate 不需要在其分配的存储中创建数组对象
不需要在其分配的存储中创建数组对象
需要
  1. std::allocator 的成员类型 referenceconst_reference 分别定义为 T&const T&
    • 如果 T 是引用类型,则 referenceconst_reference 格式错误,因为无法形成引用到引用(引用折叠在 C++11 中引入)。
    • 如果 T 是 const 限定的,则 referenceconst_reference 相同,并且 address() 的重载集格式错误。