Namespaces
Variants

C++ named requirements: Allocator

From cppreference.net
C++ named requirements

封装了对象的访问/寻址、分配/释放以及构造/销毁策略。

每个可能需要分配或释放存储空间的标准库组件,从 std::string std::vector 及所有容器 (除 std::array (C++11 起) std::inplace_vector (C++26 起) ,到 std::shared_ptr std::function (C++17 前) ,都是通过 分配器 来实现的:即满足下列要求的类类型对象。

许多分配器要求的实现是可选的,因为所有 AllocatorAwareContainer 都通过 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 的值,通过从 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_pointer A::void_pointer 为同一类型。
A::const_void_pointer (可选) (未指定)
  • 满足 NullablePointer 要求。
  • A::pointer A::const_pointer A::void_pointer 均可转换为 A::const_void_pointer
  • B::const_void_pointer A::const_void_pointer 为同一类型。
A::value_type T
A::size_type (可选) (未指定)
  • 无符号整数类型。
  • 可表示 A 能分配的最大对象的大小。
A::difference_type (可选) (未指定)
  • 有符号整数类型。
  • 可表示 A 分配的对象中任意两个指针的差值。
A::template rebind<U>::other
(可选) [2]
B
  • 对于任意 U B::template rebind<T>::other 均为 A
指针操作
表达式 返回类型 要求
* 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... ) (可选) (未使用) xp 所指地址的预分配存储空间中,使用 args... 作为构造函数参数构造 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 ) == b A ( 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_type std::false_type 或其派生类型。
对容器操作的影响
表达式 返回类型 说明
a. select_on_container_copy_construction ( )
(可选)
A
  • 为从当前使用 a 的容器复制构造的容器提供 A 的实例。
  • (通常返回 a 的副本或默认构造的 A 。)
类型标识 别名类型 说明
A::propagate_on_container_copy_assignment
(可选)
std::true_type std::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. 另请参阅下文中的 fancy pointers
  2. rebind 仅在分配器为 SomeAllocator<T, Args> 形式的模板时才是可选的(由 std::allocator_traits 提供),其中 Args 是零个或多个额外的模板类型参数。

给定

  • x1 x2 ,作为(可能不同类型的) X::void_pointer X::const_void_pointer X::pointer X::const_pointer 类型的对象
那么,当且仅当 x1 x2 都能通过仅使用这四种类型的 static_cast 序列显式转换为 X::const_pointer 类型的两个对应对象 px1 px2 ,且表达式 px1 == px2 的计算结果为 true 时, x1 x2 即为 等值指针

给定

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

给定

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

上述要求使得比较 Container iterator const_iterator 成为可能。

分配器完整性要求

若满足以下两个条件,则类型 T 的分配器类型 X 额外满足 分配器完整性要求 (无论 T 是否为完整类型):

(C++17 起)

有状态与无状态分配器

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

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

(C++11 前)

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

(C++11 起)

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

成员类型 is_always_equal 属于 std::allocator_traits ,专门用于判断分配器类型是否为无状态的。

(since C++11)

Fancy pointers

当成员类型 pointer 不是原始指针类型时,通常被称为 "fancy pointer" 。这类指针的引入是为了支持分段式内存架构,如今被用于访问分配在非统一虚拟地址空间中的对象,这些地址空间无法通过原始指针直接访问。fancy pointer 的一个典型实例是映射地址无关指针 boost::interprocess::offset_ptr ,它使得在共享内存和内存映射文件中分配基于节点的数据结构(如 std::set )成为可能,即使这些内存在不同进程中被映射到不同地址。fancy pointers 可以独立于提供它们的分配器使用 ,通过类模板 std::pointer_traits (C++11 起) 函数 std::to_address 可用于从 fancy pointer 获取原始指针。 (C++20 起)

标准库中对花式指针和自定义大小/不同类型的支持是有条件的。实现可能要求成员类型 pointer const_pointer size_type difference_type 必须分别为 value_type* const value_type * std::size_t std::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 起)

标准库

以下标准库组件满足 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 未要求 pointer const_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_pointer const_void_pointer
恢复并推广适用范围
LWG 2447 C++11 T 可能为 volatile 限定对象类型 禁止使用这些类型
LWG 2593 C++11 移动分配器可能改变其值 禁止修改值
P0593R6 C++98 未要求 allocate 在其分配的存储中
创建数组对象
要求必须创建
  1. std::allocator 的成员类型 reference const_reference 分别定义为 T& const T&
    • 如果 T 是引用类型,则 reference const_reference 是病式构造,因为无法形成引用的引用( 引用折叠 在 C++11 中引入)。
    • 如果 T 具有 const 限定,则 reference const_reference 相同,且 address() 的重载集合是病式构造。