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