命名空间
变体
操作

C++ 命名要求: Allocator

来自 cppreference.cn
 
 
C++ 命名要求
 

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

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

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

目录

[编辑] 要求

给定

  • T,一个 非 const、非引用类型(截至 C++11)非 const 对象类型(自 C++11 起)(截至 C++17)cv-非限定对象类型(自 C++17 起)
  • A,类型 TAllocator 类型,
  • 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 的值。
内部类型
类型标识 别名类型 要求
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 指向的地址构造类型为 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
(可选)
std::true_typestd::false_type 或从中派生。
  • 如果类型 A 的任何两个分配器始终比较相等,则为 true
  • (如果未提供,则 std::allocator_traits 默认将其设置为 std::is_empty<A>::type。)
对容器操作的影响
表达式 返回类型 描述
a.select_on_container_copy_construction()
(可选)
A
  • 提供类型 A 的实例,供从当前使用 a 的容器复制构造的容器使用。
  • (通常返回 a 的副本或默认构造的 A。)
类型标识 别名类型 描述
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. 如果此分配器是 SomeAllocator<T, Args> 形式的模板,其中 Args 是零个或多个附加模板类型参数,则 rebind 才是可选的(由 std::allocator_traits 提供)。

给定

  • 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等价取值对象,而语义不变。

以上要求使得比较 Containeriteratorconst_iterator 成为可能。

分配器完整性要求

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

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

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

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

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

(截至 C++11)

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

(自 C++11 起)

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

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

(自 C++11 起)

[编辑] 花式指针

当成员类型 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++ 标准。

DR 应用于 已发布行为 正确行为
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)
要求
LWG 2108 C++11 没有办法表明分配器是无状态的 提供了 is_always_equal
LWG 2263 C++11 LWG 问题 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() 的重载集是格式错误的。