C++中std::allocator的具體使用
1.std::allocator
C++中的std::allocator默默工作在C++STL中的所有容器的內(nèi)存分配上,很多內(nèi)存池是按照std::allocator的標(biāo)準(zhǔn)來(lái)實(shí)現(xiàn)的,甚至很多開(kāi)源的內(nèi)存儲(chǔ)項(xiàng)目可以和大多數(shù)STL容器兼容,在很多場(chǎng)景下,內(nèi)存池是std::allocator的優(yōu)化。
在C++中,傳統(tǒng)new操作符將內(nèi)存分配(operator new,這里的operator new是C++的內(nèi)存分配原語(yǔ),默認(rèn)調(diào)用C語(yǔ)言中的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 newconstruct():在預(yù)分配的內(nèi)存上構(gòu)造對(duì)象(通過(guò)使用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ì)象,而不是通過(guò)傳統(tǒng)的new操作符同時(shí)分配內(nèi)存和構(gòu)造對(duì)象。
placement new的語(yǔ)法形式為:
new (pointer) Type(constructor_arguments);
其中:
pointer是指向已分配內(nèi)存的指針Type是要構(gòu)造的對(duì)象constructor_arguments是構(gòu)造函數(shù)的參數(shù)
placement new的工作原理是,不調(diào)用operator new來(lái)分配內(nèi)存,而是在給定的內(nèi)存地址上直接調(diào)用構(gòu)造函數(shù),最后返回傳入的指針(將指針類型轉(zhuǎn)換為目標(biāo)類型)。placement new由C++標(biāo)準(zhǔn)庫(kù)提供默認(rèn)實(shí)現(xiàn),不可重載:
// 標(biāo)準(zhǔn)庫(kù)中的 placement new 聲明(不可重載)
void* operator new(size_t, void* ptr) noexcept {
return ptr; // 直接返回傳入的指針
}
乍一看,這個(gè)placement new的實(shí)現(xiàn)什么都沒(méi)干,是如何完成對(duì)象的構(gòu)造呢?其實(shí)是依靠語(yǔ)法來(lái)進(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)存分配原語(yǔ),默認(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的典型場(chǎng)景如下:
// 僅分配內(nèi)存,不構(gòu)造對(duì)象 void* raw_mem = operator new(sizeof(MyClass)); // 需要手動(dòng)構(gòu)造對(duì)象(例如通過(guò) 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)存來(lái)源 | 通常來(lái)自于堆(可通過(guò)重載自定義) | 由程序員預(yù)先提供 |
| 語(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開(kāi)始的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 后推薦通過(guò) std::allocator_traits 訪問(wèn)接口,以支持自定義分配器的可選方法。
按照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";
}
// 支持同類型分配器比較(無(wú)狀態(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 及之后版本中,推薦通過(guò) std::allocator_traits 訪問(wèn)分配器接口,而非直接調(diào)用分配器的方法。這是因?yàn)?allocator_traits 提供了一種統(tǒng)一且安全的方式來(lái)與分配器交互,即使自定義分配器沒(méi)有實(shí)現(xiàn)某些可選方法,也能通過(guò)默認(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)庫(kù)容器(如
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,通過(guò)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è)極簡(jiǎn)分配器的例子:
#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)存(通過(guò) allocator_traits)
auto p = std::allocator_traits<Alloc>::allocate(alloc, 1);
// 2. 構(gòu)造對(duì)象(即使 SimpleAllocator 沒(méi)有 construct 方法?。?
std::allocator_traits<Alloc>::construct(alloc, p, 42); // 調(diào)用 Widget(42)
// 3. 析構(gòu)對(duì)象(即使 SimpleAllocator 沒(méi)有 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)文章
淺談哈希表存儲(chǔ)效率一般不超過(guò)50%的原因
下面小編就為大家?guī)?lái)一篇淺談哈希表存儲(chǔ)效率一般不超過(guò)50%的原因。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01
C語(yǔ)言實(shí)現(xiàn)中綴表達(dá)式轉(zhuǎn)換為后綴表達(dá)式
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)中綴表達(dá)式轉(zhuǎn)換為后綴表達(dá)式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04
C語(yǔ)言項(xiàng)目爬樓梯的兩種實(shí)現(xiàn)方法參考
今天小編就為大家分享一篇關(guān)于C語(yǔ)言項(xiàng)目爬樓梯的兩種實(shí)現(xiàn)方法參考,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02
C++ 類和對(duì)象從基礎(chǔ)語(yǔ)法到高級(jí)特性深度解析
本文深入探討了C++類和對(duì)象的核心概念,包括類的定義、訪問(wèn)控制、實(shí)例化、this指針、默認(rèn)成員函數(shù)(構(gòu)造、析構(gòu)、拷貝構(gòu)造、賦值重載)、以及高級(jí)特性,感興趣的朋友跟隨小編一起看看吧2026-01-01
Qt模仿實(shí)現(xiàn)文字浮動(dòng)字母的效果
這篇文章主要介紹了通過(guò)Qt實(shí)現(xiàn)的文字浮動(dòng)的效果,效果很簡(jiǎn)單就是文本向上移動(dòng),在移動(dòng)過(guò)程中文字整體變大或縮小。感興趣的可以試一試2022-01-01
cmake跨平臺(tái)構(gòu)建工具的學(xué)習(xí)筆記
CMake是一個(gè)跨平臺(tái)的安裝/編譯工具,通過(guò)CMake我們可以通過(guò)簡(jiǎn)單的語(yǔ)句來(lái)描述所有平臺(tái)的安裝/編譯過(guò)程,下面這篇文章主要給大家介紹了關(guān)于cmake跨平臺(tái)構(gòu)建工具的相關(guān)資料,需要的朋友可以參考下2023-02-02
基于C語(yǔ)言實(shí)現(xiàn)的TCP服務(wù)器的流程分析
本文詳細(xì)介紹了如何使用C語(yǔ)言編寫一個(gè)簡(jiǎn)單的TCP服務(wù)器,包括創(chuàng)建套接字、綁定IP和端口、監(jiān)聽(tīng)連接請(qǐng)求、接受客戶端連接、數(shù)據(jù)接收與發(fā)送以及關(guān)閉套接字等步驟,最后通過(guò)一個(gè)簡(jiǎn)單的示例展示了TCP服務(wù)器的基本實(shí)現(xiàn)過(guò)程2024-10-10
C++實(shí)現(xiàn)動(dòng)態(tài)煙花代碼
這篇文章主要介紹了利用C++實(shí)現(xiàn)的放煙花程序,用到了EGE圖形庫(kù),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C++有一定幫助,需要的可以參考一下2023-01-01

