C++ 具名要求:分配器 (Allocator)

出自cppreference.com


 
 
C++ 具名要求
 

封裝訪問/尋址,分配/解分配,以及對象的構造/析構的策略。

可能需要分配或釋放內存的每個標準庫組件,從 std::stringstd::vectorstd::array std::inplace_vector (C++26 起)以外的(C++11 起)所有容器,到 std::shared_ptrstd::function(C++17 前),都通過分配器 (Allocator) 進行這些操作:分配器是滿足下列要求的類類型對象。

許多分配器要求的實現是可選的,因為所有知分配器容器 (AllocatorAwareContainer) 都通過 std::allocator_traits 訪問分配器,而 std::allocator_traits 提供這些要求的默認實現。

要求

給定

  • T無 const 限定的非引用類型(C++11 前)無 cv 限定的對象類型(C++11 起)
  • AT 類型的分配器 (Allocator) 類型
  • aA 類型的對象
  • B,某個無 cv 限定的對象類型 U 的對應分配器 (Allocator) 類型(由重綁定 A 獲得)
  • bB 類型的對象
  • pstd::allocator_traits<A>::pointer 類型的值,由調用 allocator_traits<A>::allocate() 獲得
  • cpstd::allocator_traits<A>::const_pointer 類型的值,從 p 轉換獲得
  • vpstd::allocator_traits<A>::void_pointer 類型的值,從 p 轉換獲得
  • cvpstd::allocator_traits<A>::const_void_pointer 類型的值,從 cp 或從 vp 轉換獲得
  • xp,指向某個無 cv 限定類型 X 的可解引用的指針
  • r,由表達式 *p 獲得的 T 類型的左值
  • nstd::allocator_traits<A>::size_type 類型的值
內部類型
類型標識 別名使用的類型 要求
A::pointer (可選) (未指定)[1]
A::const_pointer (可選) (未指定)
A::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>::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),但可能以未指定的方式使用 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 指向的存儲,該值必須由之前調用 allocate allocate_at_least(C++23 起) 返回且未因介入的對 deallocate 的調用而失效。n 必須匹配先前傳給 allocate 的值,或介於經由 allocate_at_last 所請求和返回的���素數之間(可以等於任一邊界)(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 或從它們派生。
  • 如果複製賦值使用 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. 參閱後述綴飾指針
  2. rebind 只有在分配器是形式為 SomeAllocator<T, Args> 的模板(其中 Args 是零或更多個額外的類型模板形參)時才是可選的(由 std::allocator_traits 提供)。

給定

  • x1x2,(可能不同的)類型 X::void_pointerX::const_void_pointerX::pointerX::const_pointer 的對象。
那麼當且僅當 x1x2 能用僅使用這四個類型的一系列 static_cast 顯式轉換成 X::const_pointer 類型的兩個對應的對象 px1px2,且表達式 px1 == px2 求值為 true 時,x1x2等價值的指針值。

給定

  • w1w2X::void_pointer 類型的對象
那麼對於表達式 w1 == w2w1 != w2,可以將一個或兩個對象替換成等價值X::const_void_pointer 類型的對象而不更改語義。

給定

  • p1p2X::pointer 類型的對象
那麼對於表達式 p1 == p2p1 != p2p1 < p2p1 <= p2p1 >= p2p1 > p2p1 - p2,可以將一個或兩個對象替換成等價值X::const_pointer 類型的對象而不更改語義。

以上要求使得能比較容器 (Container) iteratorconst_iterator

分配器完整性要求

如果無論 T 是否為完整類型都滿足以下所有條件,那麼針對類型 T 的分配器類型 X 還額外滿足分配器完整性要求

  • X 是完整類型
  • value_type 之外,std::allocator_traits<X> 的所有成員都是完整類型。
(C++17 起)

有狀態與無狀態分配器

每個分配器 (Allocator) 類型要麼是有狀態要麼是無狀態的。通常來說,有狀態分配器類型可以有不相等的值,它們代表不同的內存資源,而無狀態分配器類型代表單一內存資源。

儘管不要求自定義分配器為無狀態,但標準庫中是否及如何使用分配器是由實現定義的。如果實現不支持使用不相等的分配器值,那麼這種使用可能導致實現定義的運行時錯誤或未定義行為。

(C++11 前)

定製分配器可含有狀態。每個容器或其他知分配器對象都存儲所提供分配器的一個實例,並通過 std::allocator_traits 控制分配器的替換。

(C++11 起)

無狀態分配器類型的實例始終比較相等。無狀態分配器類型常實現為空類並適合空基類優化

std::allocator_traits 的成員類型 is_always_equal 有意用於確定分配器類型是否為無狀態。

(C++11 起)

綴飾指針

當成員類型 pointer 不是原生指針時,它通常被稱為「綴飾指針(fancy 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 << "在 " << std::hex << std::showbase
                  << reinterpret_cast<void*>(p) << std::dec
                  << (alloc ? " 分配 " : " 解分配 ")
                  << sizeof(T) * n << " 个字节\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);
}

可能的輸出:

在 0x2020c20 分配 32 个字节
在 0x2023c60 分配 64 个字节
在 0x2020c20 解分配 32 个字节
在 0x2023c60 解分配 64 个字节

缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 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) 需要
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() 的重載集非良構