C++ 命名需求: 分配器 (Allocator)
封裝了物件的訪問/定址、分配/釋放和構造/析構的策略。
每一個可能需要分配或釋放儲存的標準庫元件,從 std::string、std::vector 以及所有容器,除了 std::array(自 C++11 起) 和 std::inplace_vector(自 C++26 起),到 std::shared_ptr 和 std::function(直到 C++17),都透過一個 分配器 (Allocator) 來完成:一個滿足以下要求的類型別物件。
許多分配器要求的實現是可選的,因為所有 分配器感知容器 (AllocatorAwareContainer) 都透過 std::allocator_traits 間接訪問分配器,而 std::allocator_traits 提供了這些要求的預設實現。
目錄 |
[編輯] 要求
給定
-
T,一個非 const、非引用型別(直到 C++11)非 const 物件型別(自 C++11 起)(直到 C++17) cv 不合格物件型別(自 C++17 起), -
A,型別T的分配器 (Allocator) 型別, - 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 的值。
| 型別 ID | 別名型別 | 要求 |
|---|---|---|
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 的儲存,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 |
| |
| 宣告 | 效果 | 要求 |
| 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) 的先前值。 |
|
| 型別 ID | 別名型別 | 要求 |
A::is_always_equal(可選) |
std::true_type 或 std::false_type 或派生自此類。 |
|
| 表示式 | 返回型別 | 描述 |
|---|---|---|
| a.select_on_container_copy_construction() (可選) |
A
|
|
| 型別 ID | 別名型別 | 描述 |
A::propagate_on_container_copy_assignment(可選) |
std::true_type 或 std::false_type 或派生自此類。 |
|
A::propagate_on_container_move_assignment(可選) |
| |
A::propagate_on_container_swap(可選) |
|
注意
- ↑ 另請參閱下面的 花式指標。
- ↑
rebind僅在當此分配器是SomeAllocator<T, Args>形式的模板時才可選(由 std::allocator_traits 提供),其中Args是零個或多個額外的模板型別引數。
給定
- x1 和 x2,型別為
X::void_pointer、X::const_void_pointer、X::pointer或X::const_pointer的物件(可能不同)
- 那麼,x1 和 x2 是等價值的指標值,當且僅當 x1 和 x2 都可以透過僅使用這四種類型的 static_cast 序列顯式轉換為型別
X::const_pointer的兩個相應物件 px1 和 px2,並且表示式 px1 == px2 求值為 true。
給定
- 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 起) |
[編輯] 花式指標 (Fancy pointers)
當成員型別 pointer 不是原始指標型別時,它通常被稱為 “花式指標”。引入此類指標是為了支援分段記憶體架構,今天它們用於訪問與原始指標訪問的同構虛擬地址空間不同的地址空間中分配的物件。花式指標的一個示例是對映地址無關指標 boost::interprocess::offset_ptr,它使得在共享記憶體和記憶體對映檔案中分配基於節點的_資料結構(如 std::set)成為可能,這些檔案在每個程序中對映到不同的地址。花式指標可以獨立於提供它們的分配器使用,透過類模板 std::pointer_traits(自 C++11 起)。 函式 std::to_address 可用於從花式指標獲取原始指標。(自 C++20 起)
|
在標準庫中使用花式指標和自定義大小/不同型別是條件支援的。實現可能要求成員型別 |
(C++11 前) |
概念對於查詢物件 std::get_allocator 的定義,定義了以下僅用於闡釋的概念。
僅用於闡釋的概念 /*simple-allocator*/ 定義了分配器 (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 << (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) 分配器不需要支援複製賦值 (C++98) 和移動賦值 (C++11) |
需要 |
| LWG 2108 | C++11 | 無法表示分配器是無狀態的 | 提供 is_always_equal |
| LWG 2263 | C++11 | LWG issue 179 的解決方案在 C++11 中意外丟失 並且未推廣到 void_pointer 和 const_void_pointer |
恢復並推廣 |
| LWG 2447 | C++11 | T 可以是 volatile 限定物件型別 |
禁止這些型別 |
| LWG 2593 | C++11 | 從分配器移動可能修改其值 | 禁止修改 |
| P0593R6 | C++98 | allocate 不需要在其分配的儲存中建立陣列物件不需要在其分配的儲存中建立陣列物件 |
需要 |
- ↑ std::allocator 的成員型別
reference和const_reference分別定義為T&和const T&。