C++中std::allocator的具體使用
1.std::allocator
C++中的std::allocator
默默工作在C++STL中的所有容器的內存分配上,很多內存池是按照std::allocator
的標準來實現(xiàn)的,甚至很多開源的內存儲項目可以和大多數(shù)STL容器兼容,在很多場景下,內存池是std::allocator
的優(yōu)化。
在C++中,傳統(tǒng)new
操作符將內存分配(operator new
,這里的operator new
是C++的內存分配原語,默認調用C語言中的malloc
,只是進行內存分配)和對象構造(構造函數(shù))耦合。即new
運算符需要同時完成內存分配和對象構造兩個操作。
std::allocator
將解耦內存分配和對象構造這兩個操作,按照C++11的標準,實現(xiàn)一個std::allocator
需要包含以下的元素和方法:
value_type
:將模板的參數(shù)類型T
定義為value_type
,如using value_type = T;
或者typedef T value_type;
allocate()
:僅分配原始內存,功能就類似opeartor new
construct()
:在預分配的內存上構造對象(通過使用C++中的placement new機制)destroy()
:析構對象但不釋放內存deallocate()
:釋放原始內存(類似于operator delete
)
注釋: https://cplusplus.com/reference/memory/allocator/
1.1C++中的placement new 和operator new
placement new 是C++中一種特使的內存分配的對象構造機制,它允許在已分配的內存上直接構造對象,而不是通過傳統(tǒng)的new
操作符同時分配內存和構造對象。
placement new的語法形式為:
new (pointer) Type(constructor_arguments);
其中:
pointer
是指向已分配內存的指針Type
是要構造的對象constructor_arguments
是構造函數(shù)的參數(shù)
placement new的工作原理是,不調用operator new
來分配內存,而是在給定的內存地址上直接調用構造函數(shù),最后返回傳入的指針(將指針類型轉換為目標類型)。placement new由C++標準庫提供默認實現(xiàn),不可重載:
// 標準庫中的 placement new 聲明(不可重載) void* operator new(size_t, void* ptr) noexcept { return ptr; // 直接返回傳入的指針 }
乍一看,這個placement new的實現(xiàn)什么都沒干,是如何完成對象的構造呢?其實是依靠語法來進行創(chuàng)建的:
new (pointer) Type(constructor_arguments);
這里仍然調用了Type(constructor_arguments)
,即調用了對象的構造函數(shù),在pointer
指定的內存上進行構造,舉個例子:
#include <iostream> struct Example { int value; Example(int val) : value(val) { std::cout << "Constructed at " << this << " with value " << value << std::endl; } ~Example() { std::cout << "Destructed at " << this << std::endl; } }; int main() { // 手動分配一塊內存 void* buffer = operator new(sizeof(Example)); // 使用placement new在這塊內存上構造對象 Example* obj = new (buffer) Example(42); // 顯式調用析構函數(shù)(這很重要?。? obj->~Example(); // 釋放內存 operator delete(buffer); return 0; }
輸出為:
Constructed at 0x7fec4c400030 with value 42
Destructed at 0x7fec4c400030
operator new
是C++的內存分配原語,默認調用malloc
進行內存分配,返回void*
,指向未初始化的原始內存,可以重載operator new
以自定義其內存分配行為:
// 自定義全局 operator new void* operator new(size_t size){ std::cout << "Allocating " << size << " bytes\n"; return malloc(size); }
使用opeartor new
和placement new的典型場景如下:
// 僅分配內存,不構造對象 void* raw_mem = operator new(sizeof(MyClass)); // 需要手動構造對象(例如通過 placement new) MyClass* obj = new (raw_mem) MyClass(); // 調用構造函數(shù) // 必須手動析構和釋放 obj->~MyClass(); operator delete(raw_mem);
核心區(qū)別:
特性 | operator new | placement new |
---|---|---|
作用 | 僅分配原始內存(不構造對象) | 在已分配的內存上構造對象 |
是否調用構造函數(shù) | 否 | 是 |
內存來源 | 通常來自于堆(可通過重載自定義) | 由程序員預先提供 |
語法 | void* p = operator new(size) | new (ptr) Type(args...) |
是否可重載 | 可重載全局或類特定的operator new | 不能重載,已經(jīng)有固定實現(xiàn) |
1.2一個custom allocator的實現(xiàn)
一個自定義的allocator
需要實現(xiàn)以下的方法:
方法 | 描述 | 等效操作 |
---|---|---|
allocate(n) | 分配n* sizeof(T)字節(jié) | operator new |
deallocate(p, n) | 釋放從p開始的n個元素 | operator delete |
construct(p, args) | 在p構造對象(C++17已棄用) | new(p) T(args...) |
destroy(p) | 析構p處對象(C++17已棄用) | p->~T() |
注釋:C++17 后推薦通過 std::allocator_traits 訪問接口,以支持自定義分配器的可選方法。
按照C++11的標準實現(xiàn)一個allocator
:
#include <iostream> #include <vector> template<typename T> class TrackingAllocator { public: using value_type = T; TrackingAllocator() = default; // 支持 Rebinding(重新綁定) template<typename U> TrackingAllocator(const TrackingAllocator<U>&) {} T* allocate(size_t n) { size_t bytes = n * sizeof(T); std::cout << "Allocating " << bytes << " bytes\n"; return static_cast<T*>(::operator new(bytes)); } void deallocate(T* p, size_t n) { ::operator delete(p); std::cout << "Deallocating " << n * sizeof(T) << " bytes\n"; } // 支持同類型分配器比較(無狀態(tài)) bool operator==(const TrackingAllocator&) { return true; } bool operator!=(const TrackingAllocator&) { return false; } }; // 使用示例 int main() { // 使用自定義分配器 std::vector<int, TrackingAllocator<int>> vec; vec.push_back(42); // 輸出分配信息 vec.push_back(13); // 輸出分配信息 // 清空向量 vec.clear(); // 輸出釋放信息 return 0; }
輸出:
Allocating 4 bytes
Allocating 8 bytes
Deallocating 4 bytes
Deallocating 8 bytes
1.3使用std::allocator_traits實現(xiàn)allocator
在 C++17 及之后版本中,推薦通過 std::allocator_traits
訪問分配器接口,而非直接調用分配器的方法。這是因為 allocator_traits
提供了一種統(tǒng)一且安全的方式來與分配器交互,即使自定義分配器沒有實現(xiàn)某些可選方法,也能通過默認實現(xiàn)正常工作。
- 兼容性:即使自定義分配器未實現(xiàn)某些方法(如
construct
/destroy
),allocator_traits
會提供默認實現(xiàn)。 - 靈活性:允許分配器僅實現(xiàn)必要的接口,其余由
allocator_traits
補充。 - 標準化:所有標準庫容器(如
std::vector
、std::list
)內部都使用allocator_traits
而非直接調用分配器。
關鍵接口對比(使用C++11標準 vs. C++17標準)
操作 | C++11,直接調用分配器alloc | C++17,通過allocator_traits(std::allocator_traits<Alloc>) |
---|---|---|
分配內存 | alloc.allocate(n) | allocator_traits<Alloc>::allocate(alloc, n) |
釋放內存 | alloc.deallocate(p, n) | allocator_traits<Alloc>::deallocate(alloc, p, n) |
構造對象 | alloc.construct(p, args) | allocator_traits<Alloc>::construct(alloc, p, args...) |
析構對象 | alloc.destroy(p) | allocator_traits<Alloc>::destroy(alloc, p) |
獲取最大大小 | alloc.max_size() | allocator_traits<Alloc>::max_size(alloc) |
重新綁定分配器類型 | alloc.rebind<U>::other | allocator_traits<Alloc>::rebind_alloc<U> |
注釋:C++17 后 construct 和 destroy 被廢棄,推薦直接使用 std::allocator_traits 或 placement new/顯式析構。
舉個極簡分配器的例子:
#include <iostream> #include <memory> // std::allocator_traits template <typename T> struct SimpleAllocator { using value_type = T; // 必須提供 allocate 和 deallocate T* allocate(size_t n) { return static_cast<T*>(::operator new(n * sizeof(T))); } void deallocate(T* p, size_t n) { ::operator delete(p); } // 不提供 construct/destroy,由 allocator_traits 提供默認實現(xiàn) }; struct Widget { int id; Widget(int i) : id(i) { std::cout << "Construct Widget " << id << "\n"; } ~Widget() { std::cout << "Destroy Widget " << id << "\n"; } }; int main() { using Alloc = SimpleAllocator<Widget>; Alloc alloc; // 1. 分配內存(通過 allocator_traits) auto p = std::allocator_traits<Alloc>::allocate(alloc, 1); // 2. 構造對象(即使 SimpleAllocator 沒有 construct 方法!) std::allocator_traits<Alloc>::construct(alloc, p, 42); // 調用 Widget(42) // 3. 析構對象(即使 SimpleAllocator 沒有 destroy 方法?。? std::allocator_traits<Alloc>::destroy(alloc, p); // 4. 釋放內存 std::allocator_traits<Alloc>::deallocate(alloc, p, 1); return 0; }
輸出:
Construct Widget 42
Destroy Widget 42
一個更復雜的自定義分配器示例(帶狀態(tài))
#include <iostream> #include <memory> // std::allocator_traits template <typename T> class TrackingAllocator { size_t total_allocated = 0; public: using value_type = T; T* allocate(size_t n) { total_allocated += n * sizeof(T); std::cout << "Allocated " << n * sizeof(T) << " bytes (Total: " << total_allocated << ")\n"; return static_cast<T*>(::operator new(n * sizeof(T))); } void deallocate(T* p, size_t n) { total_allocated -= n * sizeof(T); std::cout << "Deallocated " << n * sizeof(T) << " bytes (Remaining: " << total_allocated << ")\n"; ::operator delete(p); } // 支持比較(相同類型的 TrackingAllocator 才等價) bool operator==(const TrackingAllocator& other) const { return false; // 有狀態(tài),不同實例不能混用 } bool operator!=(const TrackingAllocator& other) const { return true; } }; int main() { using Alloc = TrackingAllocator<int>; Alloc alloc1, alloc2; auto p1 = std::allocator_traits<Alloc>::allocate(alloc1, 2); auto p2 = std::allocator_traits<Alloc>::allocate(alloc2, 3); // 必須用相同的 allocator 實例釋放! std::allocator_traits<Alloc>::deallocate(alloc1, p1, 2); std::allocator_traits<Alloc>::deallocate(alloc2, p2, 3); return 0; }
輸出:
Allocated 8 bytes (Total: 8)
Allocated 12 bytes (Total: 12)
Deallocated 8 bytes (Remaining: 0)
Deallocated 12 bytes (Remaining: 0)
到此這篇關于C++中std::allocator的具體使用的文章就介紹到這了,更多相關C++ std::allocator內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
QT實現(xiàn)將兩個時間相加的算法[hh:?mm?+?hh:?mm]的示例代碼
本文主要介紹了QT實現(xiàn)將兩個時間相加的算法[hh:?mm?+?hh:?mm]的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07C++實現(xiàn)雙目立體匹配Census算法的示例代碼
這篇文章主要為大家詳細介紹了如何利用C++實現(xiàn)雙目立體匹配Census算法,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2022-08-08C/C++百行代碼實現(xiàn)熱門游戲消消樂功能的示例代碼
這篇文章主要介紹了C/C++百行代碼實現(xiàn)熱門游戲消消樂功能的示例代碼,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07C++實現(xiàn)LeetCode(30.串聯(lián)所有單詞的子串)
這篇文章主要介紹了C++實現(xiàn)LeetCode(30.串聯(lián)所有單詞的子串),本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-07-07