命名空间
变体
操作

C++ 命名需求: 分配器

来自 cppreference.com
 
 
C++ 命名需求
 

封装用于访问/寻址、分配/释放和对象构造/销毁的策略。

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

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

内容

[编辑] 需求

鉴于

  • T,一个非 const、非引用类型(直到 C++11)非 const 对象类型(自 C++11 起)(直到 C++17)cv 无限定对象类型(自 C++17 起)
  • A,类型 T 的一个 分配器 类型,
  • a,类型 A 的一个对象,
  • B,对应于某个 cv 无限定对象类型 U分配器 类型(通过重新绑定 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 的值,通过从 cpvp 转换获得,
  • xp,指向某个 cv 无限定对象类型 X 的可解引用指针,
  • r,通过表达式 *p 获得的类型 T 的左值,
  • n,类型 std::allocator_traits<A>::size_type 的值。
内部类型
类型标识 别名类型 需求
A::pointer (可选) (未指定)[1]
A::const_pointer (可选) (未指定)
A::void_pointer (可选) (未指定)
  • 满足 可空指针
  • A::pointer 可转换为 A::void_pointer
  • B::void_pointerA::void_pointer 是相同的类型。
A::const_void_pointer (可选) (未指定)
  • 满足 可空指针
  • 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) 相同,但可能以未指定的方式使用 cvp (nullptr 或从 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_least 请求的元素数量和返回的元素数量之间(可以等于任一边界)(自 C++23 起) 的值匹配。不会抛出异常。
a.max_size() (可选) A::size_type 可以传递给 A::allocate() 的最大值。
a.construct(xp, args...) (可选) (未使用) 使用 args... 作为构造函数参数,在先前分配的存储空间(位于 xp 指向的地址处)中构造类型为 X 的对象。
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 都可以使用一系列的 static_cast(仅使用这四种类型)显式转换为类型为 X::const_pointer 的两个相应对象 px1px2,并且表达式 px1 == px2 的计算结果为 true,则 x1x2等值的指针值。

鉴于

  • 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 类型对象替换其中一个或两个对象,语义不变。

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

分配器完整性要求

如果以下两点都成立,则类型 T 的分配器类型 X 另外满足分配器完整性要求,无论 T 是否是完整类型:

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

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

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

虽然自定义分配器不需要是无状态的,但标准库中是否有状态分配器的使用以及如何使用是实现定义的。如果实现不支持此类使用,则使用不相等的分配器值可能会导致实现定义的运行时错误或未定义的行为。

(直到 C++11)

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

(自 C++11 起)

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

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

(自 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*/ 定义了 分配器 要求的最小可用性约束。

(自 C++26 起)

[编辑] 标准库

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

默认分配器
(类模板) [编辑]
实现多级容器的多级分配器
(类模板) [编辑]
一个基于其构造的 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 可以是限定为常量的类型或引用类型,
这可能会导致 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 可以是限定为易变的类型 禁止这些类型
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() 的重载集将是无效的。