C++ 具名要求:分配器 (Allocator)
封裝訪問/尋址,分配/解分配,以及對象的構造/析構的策略。
可能需要分配或釋放內存的每個標準庫組件,從 std::string、std::vector 和除 std::array 與 std::inplace_vector (C++26 起)以外的(C++11 起)所有容器,到 std::shared_ptr 和 std::function(C++17 前),都通過分配器 (Allocator) 進行這些操作:分配器是滿足下列要求的類類型對象。
許多分配器要求的實現是可選的,因為所有知分配器容器 (AllocatorAwareContainer) 都通過 std::allocator_traits 訪問分配器,而 std::allocator_traits 提供這些要求的默認實現。
要求
給定
T,無 const 限定的非引用類型(C++11 前)無 cv 限定的對象類型(C++11 起)A,T類型的分配器 (Allocator) 類型a,A類型的對象B,某個無 cv 限定的對象類型U的對應分配器 (Allocator) 類型(由重綁定A獲得)b,B類型的對象p,std::allocator_traits<A>::pointer類型的值,由調用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 (可選)
|
(未指定) |
|
A::const_void_pointer (可選)
|
(未指定) |
|
A::value_type
|
T
|
|
A::size_type (可選)
|
(未指定) |
|
A::difference_type (可選)
|
(未指定) |
|
A::template rebind<U>::other (可選)[2] |
B
|
|
| 表達式 | 返回類型 | 要求 |
|---|---|---|
*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_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
|
| |
| 聲明 | 效果 | 要求 |
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 = 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::propagate_on_container_copy_assignment(可選) |
std::true_type 或 std::false_type 或從它們派生。 |
|
A::propagate_on_container_move_assignment(可選) |
| |
A::propagate_on_container_swap(可選) |
|
註:
給定
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。
分配器完整性要求如果無論
|
(C++17 起) |
有狀態與無狀態分配器
每個分配器 (Allocator) 類型要麼是有狀態要麼是無狀態的。通常來說,有狀態分配器類型可以有不相等的值,它們代表不同的內存資源,而無狀態分配器類型代表單一內存資源。
|
儘管不要求自定義分配器為無狀態,但標準庫中是否及如何使用分配器是由實現定義的。如果實現不支持使用不相等的分配器值,那麼這種使用可能導致實現定義的運行時錯誤或未定義行為。 |
(C++11 前) |
|
定製分配器可含有狀態。每個容器或其他知分配器對象都存儲所提供分配器的一個實例,並通過 std::allocator_traits 控制分配器的替換。 |
(C++11 起) |
無狀態分配器類型的實例始終比較相等。無狀態分配器類型常實現為空類並適合空基類優化。
|
std::allocator_traits 的成員類型 |
(C++11 起) |
綴飾指針
當成員類型 pointer 不是原生指針時,它通常被稱為「綴飾指針(fancy pointer)」。這種指針曾為支持分段內存架構而引入,並在當今用於訪問在某些不同於原生指針所訪問的同質虛擬地址空間的地址空間中所分配的對象。綴飾指針的一個實例是映射的不依賴地址指針 boost::interprocess::offset_ptr,它使得在每個進程中映射到不同地址的共享內存和內存映射文件中分配 std::set 一類的基於結點的數據結構變得可行。通過類模板 std::pointer_traits,(C++11 起)可以獨立於提供綴飾指針的分配器而使用它們。能用函數 std::to_address 從綴飾指針獲得裸指針。(C++20 起)
|
在標準庫中使用綴飾指針和定製的大小/差類型是條件性支持的。實現可以要求成員類型 |
(C++11 前) |
概念為了定義查詢對象 std::get_allocator,定義以下僅用於闡述的概念。
僅用於闡述的概念 |
(C++26 起) |
標準庫
下列標準庫組件滿足分配器 (Allocator) 要求:
| 默認的分配器 (類模板) | |
(C++11) |
為多級容器實現的多級分配器 (類模板) |
(C++17) |
以 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 | 未要求 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 在其所分配的存儲中創建數組
|
已要求 |