1.為什么需要 allocator?
在 C++ 中,動態內存管理通常通過?new
?和?delete
?完成:
int* p = new int; // 分配內存 + 構造對象
delete p; // 析構對象 + 釋放內存
但?new
?和?delete
?有兩個問題:
-
耦合性:將內存分配和對象構造合并為一個操作。
-
靈活性不足:無法適配不同的內存管理策略(如內存池、共享內存等)。
allocator
?類的核心目標就是解耦內存分配和對象構造,提供更靈活的內存管理。
?2.allocator 的核心作用
std::allocator
?是標準庫容器(如?vector
,?list
,?map
?等)默認使用的內存分配器。它主要有以下作用:
1. 分離內存分配和對象構造
-
分配內存:先分配原始內存塊,但不構造對象。
-
構造對象:在已分配的內存上手動構造對象。
-
析構對象:手動析構對象,但不釋放內存。
-
釋放內存:最終釋放原始內存塊。
#include <memory>std::allocator<int> alloc;// 1. 分配內存(未初始化)
int* p = alloc.allocate(5); // 分配 5 個 int 的空間// 2. 構造對象
for (int i = 0; i < 5; ++i) {alloc.construct(p + i, i); // 在 p[i] 處構造 int 對象,值為 i
}// 3. 析構對象
for (int i = 0; i < 5; ++i) {alloc.destroy(p + i); // 析構 p[i] 處的對象
}// 4. 釋放內存
alloc.deallocate(p, 5);
2. 支持自定義內存管理策略
通過自定義?allocator
,可以實現:
-
內存池:預分配大塊內存,減少碎片。
-
共享內存:在進程間共享內存區域。
-
性能優化:針對特定場景優化內存分配速度。
3.allocator 的典型使用場景
場景 1:標準庫容器
所有標準庫容器(如?std::vector
)默認使用?std::allocator
:
template <class T, class Allocator = std::allocator<T>>
class vector {
private:T* data_ = nullptr; // 指向動態數組的指針size_t size_ = 0; // 當前元素數量size_t capacity_ = 0; // 當前分配的內存容量Allocator allocator_; // 內存分配器對象public:// ... 保留已有構造函數 ...// 賦值運算符(拷貝并交換 idiom)vector& operator=(const vector& other) {if (this != &other) {vector temp(other); // 利用拷貝構造函數swap(*this, temp); // 交換資源}return *this;}// 移動賦值運算符vector& operator=(vector&& other) noexcept {if (this != &other) {clear();allocator_.deallocate(data_, capacity_);data_ = other.data_;size_ = other.size_;capacity_ = other.capacity_;allocator_ = std::move(other.allocator_);other.data_ = nullptr;other.size_ = other.capacity_ = 0;}return *this;}// 交換兩個vector(noexcept保證)friend void swap(vector& a, vector& b) noexcept {using std::swap;swap(a.data_, b.data_);swap(a.size_, b.size_);swap(a.capacity_, b.capacity_);swap(a.allocator_, b.allocator_);}// 添加emplace_back支持template <class... Args>void emplace_back(Args&&... args) {if (size_ >= capacity_) {reserve(capacity_ ? capacity_ * 2 : 1);}allocator_.construct(data_ + size_++, std::forward<Args>(args)...);}// 完善reserve的異常安全void reserve(size_t new_cap) {if (new_cap <= capacity_) return;T* new_data = allocator_.allocate(new_cap);size_t i = 0;try {for (; i < size_; ++i) {allocator_.construct(new_data + i, std::move_if_noexcept(data_[i]));allocator_.destroy(data_ + i);}} catch (...) {// 回滾已構造元素for (size_t j = 0; j < i; ++j) {allocator_.destroy(new_data + j);}allocator_.deallocate(new_data, new_cap);throw;}allocator_.deallocate(data_, capacity_);data_ = new_data;capacity_ = new_cap;}// ... 保留其他已有方法 ...
};
要點說明:
1. 使用分配器進行內存管理(allocate/deallocate)
2. 實現RAII原則,在析構函數中釋放資源
3. 支持基礎操作:push_back/pop_back/clear
4. 包含移動語義優化性能
5. 實現迭代器訪問功能
6. 包含簡單的擴容策略(容量翻倍)
這個只是簡單模仿vector容器的核心機制,實際標準庫實現會更復雜(包含異常安全、優化策略等很多東西)
場景 2:自定義內存分配策略
例如,實現一個簡單的內存池:
template <typename T>
class MemoryPoolAllocator {
public:// 必需的類型定義(C++標準要求)using value_type = T; // 分配的元素類型using pointer = T*; // 指針類型using const_pointer = const T*; // 常指針類型using size_type = std::size_t; // 大小類型using difference_type = std::ptrdiff_t; // 指針差異類型// 分配器傳播特性(影響容器拷貝行為)using propagate_on_container_copy_assignment = std::true_type;using propagate_on_container_move_assignment = std::true_type;using propagate_on_container_swap = std::true_type;/*** @brief 默認構造函數(必須支持)* @note 需要保證同類型的不同allocator實例可以互相釋放內存*/MemoryPoolAllocator() noexcept = default;/*** @brief 模板拷貝構造函數(必須支持)* @tparam U 模板參數類型* @note 允許從其他模板實例化的allocator進行構造*/template <typename U>MemoryPoolAllocator(const MemoryPoolAllocator<U>&) noexcept {}/*** @brief 內存分配函數(核心接口)* @param n 需要分配的元素數量* @return 指向分配內存的指針* @exception 可能拋出std::bad_alloc或派生異常*/T* allocate(size_t n) {// TODO: 實現內存池分配邏輯// 建議方案:// 1. 計算總字節數 bytes = n * sizeof(T)// 2. 從內存池獲取對齊的內存塊// 3. 返回轉換后的指針return static_cast<T*>(::operator new(n * sizeof(T)));}/*** @brief 內存釋放函數(核心接口)* @param p 需要釋放的內存指針* @param n 釋放的元素數量* @note 必須保證p是通過allocate(n)分配的指針*/void deallocate(T* p, size_t n) noexcept {// TODO: 實現內存池回收邏輯// 建議方案:// 1. 將內存塊標記為空閑// 2. 返回內存池供后續重用::operator delete(p);}/*** @brief 分配器比較函數(必須支持)* @note 不同實例是否應該被視為相等,需根據內存池實現決定*/bool operator==(const MemoryPoolAllocator&) const noexcept { return true; // 假設所有實例使用同一內存池}bool operator!=(const MemoryPoolAllocator&) const noexcept {return false;}/*** @brief 可選:對象構造函數(C++20前需要)* @tparam Args 構造參數類型*/template <typename... Args>void construct(T* p, Args&&... args) {::new(static_cast<void*>(p)) T(std::forward<Args>(args)...);}/*** @brief 可選:對象析構函數(C++20前需要)*/void destroy(T* p) {p->~T();}
};
場景 3:避免默認初始化
默認的?new
?會調用構造函數,而?allocator
?可以先分配內存,再按需構造對象:
std::allocator<std::string> alloc;
std::string* p = alloc.allocate(3); // 僅分配內存,不構造對象// 按需構造
alloc.construct(p, "hello"); // 構造第一個 string
alloc.construct(p + 1, "world"); // 構造第二個 string
4.allocator 的關鍵接口
以下是?std::allocator
?的核心方法:
方法 | 作用 |
---|---|
allocate(n) | 分配?n ?個對象的原始內存(未初始化) |
deallocate(p, n) | 釋放內存(需先析構所有對象) |
construct(p, args) | 在位置?p ?構造對象,參數為?args |
destroy(p) | 析構?p ?處的對象 |
5.自定義 allocator 的要點
5.1. 必須提供的類型別名
自定義?allocator
?需要定義以下類型:
template <typename T>
class CustomAllocator {
public:using value_type = T; // 必須定義// 其他必要類型...
};
5.2. 實現必要接口
至少需要實現?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);
}
5.3. 支持 rebind 機制
容器可能需要分配其他類型的對象(如鏈表節點的分配器):
template <typename U>
struct rebind {using other = CustomAllocator<U>;
};
6.C++17 后的改進
C++17 引入了?std::allocator_traits
,簡化了自定義?allocator
?的實現。即使自定義分配器未實現某些接口,allocator_traits
?會提供默認實現:
template <typename Alloc>
using allocator_traits = std::allocator_traits<Alloc>;// 使用示例
auto p = allocator_traits<Alloc>::allocate(alloc, n);
7.總結
-
核心作用:解耦內存分配與對象構造,提供更靈活的內存管理。
-
默認行為:標準庫容器使用?
std::allocator
。 -
自定義場景:內存池、性能優化、特殊內存區域(如共享內存)。
-
關鍵接口:
allocate
、deallocate
、construct
、destroy
。