C++17 实现内存池
目录
C++17 内存池实现
本文基于C++17标准特性,实现了固定大小内存池(最常用、性能最优的内存池类型),包含单线程基础版、类型化对象池、线程安全版,同时讲解核心原理、C++17特性的应用,以及标准库内置方案。
一、内存池核心原理
内存池的核心目标是减少频繁new/delete的系统调用开销、缓解内存碎片、提升内存分配/释放速度,核心设计:
- 预分配一大块连续内存(内存块
Block),切分为多个固定大小的内存槽Slot - 用嵌入式空闲链表管理空闲槽(无需额外容器,直接用空闲槽存储下一个空闲槽的地址)
- 内存耗尽时自动扩容(分配新的内存块)
- 析构时批量释放所有内存块,避免内存泄漏
二、C++17 完整实现
2.1 基础工具与编译期计算
首先实现编译期对齐计算函数,以及依赖的标准头文件,完全基于C++17标准:
#include <cstddef> // std::size_t、std::align_val_t
#include <cstdlib> // std::aligned_alloc、std::free
#include <new> // std::bad_alloc、std::launder
#include <type_traits> // std::is_trivially_destructible、if constexpr
#include <stdexcept> // std::runtime_error
#include <mutex> // 线程安全版本使用
#include <utility> // std::forward
// 编译期向上对齐计算(C++17 constexpr支持)
// 要求Align必须是2的幂(符合C++标准对齐要求)
template <std::size_t Size, std::size_t Align>
constexpr std::size_t AlignUp() noexcept {
static_assert((Align & (Align - 1)) == 0, "Align must be power of 2");
return (Size + Align - 1) & ~(Align - 1);
}2.2 单线程固定大小内存池(核心实现)
纯原始内存管理,无类型绑定,适用于通用固定大小内存分配,完全使用C++17特性优化:
template <
std::size_t SlotSize, // 单个槽的大小
std::size_t SlotAlign = alignof(std::max_align_t), // 槽的对齐要求,默认最大基本对齐
std::size_t SlotsPerBlock = 1024 // 每个内存块包含的槽数量(扩容粒度)
>
class FixedMemoryPool {
private:
// 内存块结构体:串联所有内存块,用于析构时批量释放
struct Block {
Block* next;
};
// 编译期计算核心参数(零运行时开销)
// 实际槽大小:至少能存下一个指针(空闲链表用),且满足对齐要求
static constexpr std::size_t RealSlotSize = AlignUp<std::max(SlotSize, sizeof(void*)), SlotAlign>();
// 单个内存块的数据区总大小
static constexpr std::size_t BlockDataSize = RealSlotSize * SlotsPerBlock;
// 内存块的对齐要求:取Block自身对齐和槽对齐的最大值
static constexpr std::size_t BlockAlign = std::max(SlotAlign, alignof(Block));
Block* m_block_list = nullptr; // 所有内存块的链表头
void* m_free_list = nullptr; // 空闲槽链表头
std::size_t m_free_slots = 0; // 剩余空闲槽总数(统计用)
// 扩容:分配新的内存块,并初始化空闲链表
bool grow() {
// 计算内存块总大小(头部+数据区,保证对齐)
constexpr std::size_t block_header_size = AlignUp<sizeof(Block), BlockAlign>();
constexpr std::size_t total_block_size = block_header_size + BlockDataSize;
// C++17标准对齐内存分配,替代平台相关的posix_memalign/_aligned_malloc
void* block_ptr = std::aligned_alloc(BlockAlign, total_block_size);
if (!block_ptr) {
return false;
}
// placement new构造内存块头部
Block* new_block = new (block_ptr) Block;
new_block->next = m_block_list;
m_block_list = new_block;
// 计算数据区起始地址
std::byte* data_start = static_cast<std::byte*>(block_ptr) + block_header_size;
// 初始化空闲链表:把所有槽串联起来
for (std::size_t i = 0; i < SlotsPerBlock; ++i) {
void* slot = data_start + i * RealSlotSize;
*static_cast<void**>(slot) = static_cast<std::byte*>(slot) + RealSlotSize;
}
// 最后一个槽的next置空
*static_cast<void**>(data_start + (SlotsPerBlock - 1) * RealSlotSize) = nullptr;
// 更新空闲链表
m_free_list = data_start;
m_free_slots += SlotsPerBlock;
return true;
}
public:
// 构造与析构
FixedMemoryPool() = default;
~FixedMemoryPool() {
// 遍历释放所有内存块
Block* curr = m_block_list;
while (curr) {
Block* next = curr->next;
curr->~Block(); // 手动析构placement new的对象
std::free(curr); // aligned_alloc分配的内存必须用free释放
curr = next;
}
}
// 禁用拷贝与移动(避免双重释放)
FixedMemoryPool(const FixedMemoryPool&) = delete;
FixedMemoryPool& operator=(const FixedMemoryPool&) = delete;
FixedMemoryPool(FixedMemoryPool&&) = delete;
FixedMemoryPool& operator=(FixedMemoryPool&&) = delete;
// 分配一个槽的内存
[[nodiscard]] void* allocate() {
if (!m_free_list) {
if (!grow()) {
throw std::bad_alloc();
}
}
// 取出空闲链表头节点
void* slot = m_free_list;
m_free_list = *static_cast<void**>(slot);
--m_free_slots;
return slot;
}
// 归还内存到内存池
void deallocate(void* ptr) noexcept {
if (!ptr) return;
// 插入到空闲链表头部(O(1)复杂度)
*static_cast<void**>(ptr) = m_free_list;
m_free_list = ptr;
++m_free_slots;
}
// 统计辅助函数
std::size_t free_slots() const noexcept { return m_free_slots; }
std::size_t total_slots() const noexcept {
std::size_t count = 0;
Block* curr = m_block_list;
while (curr) {
count += SlotsPerBlock;
curr = curr->next;
}
return count;
}
};2.3 类型化对象池(上层封装)
基于原始内存池,封装为针对具体类型的对象池,自动处理对象的构造与析构,利用C++17特性做零成本优化:
template <typename T, std::size_t SlotsPerBlock = 1024>
class ObjectPool {
public:
using value_type = T;
// 构造与析构
ObjectPool() = default;
~ObjectPool() = default;
// 禁用拷贝与移动
ObjectPool(const ObjectPool&) = delete;
ObjectPool& operator=(const ObjectPool&) = delete;
ObjectPool(ObjectPool&&) = delete;
ObjectPool& operator=(ObjectPool&&) = delete;
// 构造对象:完美转发参数,支持任意构造函数
template <typename... Args>
[[nodiscard]] T* construct(Args&&... args) {
void* slot = m_pool.allocate();
// placement new构造对象
T* obj = new (slot) T(std::forward<Args>(args)...);
// C++17 std::launder:解决对象生命周期带来的指针别名问题
return std::launder(obj);
}
// 销毁对象:自动调用析构函数,归还内存
void destroy(T* obj) noexcept {
if (!obj) return;
// C++17 if constexpr:平凡析构类型直接跳过析构调用,零成本优化
if constexpr (!std::is_trivially_destructible_v<T>) {
obj->~T();
}
m_pool.deallocate(obj);
}
// 统计辅助函数
std::size_t free_count() const noexcept { return m_pool.free_slots(); }
std::size_t total_count() const noexcept { return m_pool.total_slots(); }
private:
FixedMemoryPool<sizeof(T), alignof(T), SlotsPerBlock> m_pool;
};2.4 线程安全版本
在基础内存池上增加互斥锁,保证多线程环境下的分配/释放安全,适用于多线程场景:
template <
std::size_t SlotSize,
std::size_t SlotAlign = alignof(std::max_align_t),
std::size_t SlotsPerBlock = 1024
>
class ThreadSafeFixedMemoryPool {
private:
struct Block {
Block* next;
};
static constexpr std::size_t RealSlotSize = AlignUp<std::max(SlotSize, sizeof(void*)), SlotAlign>();
static constexpr std::size_t BlockDataSize = RealSlotSize * SlotsPerBlock;
static constexpr std::size_t BlockAlign = std::max(SlotAlign, alignof(Block));
Block* m_block_list = nullptr;
void* m_free_list = nullptr;
std::size_t m_free_slots = 0;
mutable std::mutex m_mutex; // 互斥锁,mutable支持const函数加锁
bool grow() {
constexpr std::size_t block_header_size = AlignUp<sizeof(Block), BlockAlign>();
constexpr std::size_t total_block_size = block_header_size + BlockDataSize;
void* block_ptr = std::aligned_alloc(BlockAlign, total_block_size);
if (!block_ptr) return false;
Block* new_block = new (block_ptr) Block;
new_block->next = m_block_list;
m_block_list = new_block;
std::byte* data_start = static_cast<std::byte*>(block_ptr) + block_header_size;
for (std::size_t i = 0; i < SlotsPerBlock; ++i) {
void* slot = data_start + i * RealSlotSize;
*static_cast<void**>(slot) = static_cast<std::byte*>(slot) + RealSlotSize;
}
*static_cast<void**>(data_start + (SlotsPerBlock - 1) * RealSlotSize) = nullptr;
m_free_list = data_start;
m_free_slots += SlotsPerBlock;
return true;
}
public:
ThreadSafeFixedMemoryPool() = default;
~ThreadSafeFixedMemoryPool() {
Block* curr = m_block_list;
while (curr) {
Block* next = curr->next;
curr->~Block();
std::free(curr);
curr = next;
}
}
ThreadSafeFixedMemoryPool(const ThreadSafeFixedMemoryPool&) = delete;
ThreadSafeFixedMemoryPool& operator=(const ThreadSafeFixedMemoryPool&) = delete;
ThreadSafeFixedMemoryPool(ThreadSafeFixedMemoryPool&&) = delete;
ThreadSafeFixedMemoryPool& operator=(ThreadSafeFixedMemoryPool&&) = delete;
[[nodiscard]] void* allocate() {
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_free_list && !grow()) {
throw std::bad_alloc();
}
void* slot = m_free_list;
m_free_list = *static_cast<void**>(slot);
--m_free_slots;
return slot;
}
void deallocate(void* ptr) noexcept {
if (!ptr) return;
std::lock_guard<std::mutex> lock(m_mutex);
*static_cast<void**>(ptr) = m_free_list;
m_free_list = ptr;
++m_free_slots;
}
std::size_t free_slots() const noexcept {
std::lock_guard<std::mutex> lock(m_mutex);
return m_free_slots;
}
std::size_t total_slots() const noexcept {
std::lock_guard<std::mutex> lock(m_mutex);
std::size_t count = 0;
Block* curr = m_block_list;
while (curr) {
count += SlotsPerBlock;
curr = curr->next;
}
return count;
}
};三、C++17 核心特性应用说明
std::byte:替代传统char*表示原始内存,提供更强的类型安全,避免无意的算术运算,符合C++17的内存语义。if constexpr:编译期分支判断,对平凡析构类型跳过析构调用,实现零成本抽象,无任何运行时开销。std::aligned_alloc:C++17标准引入的对齐内存分配函数,替代平台相关接口,保证跨平台兼容性。std::launder:解决placement new后带const/引用成员的对象指针别名问题,符合C++17对象模型规范。[[nodiscard]]:C++17属性,标记分配函数的返回值不可丢弃,编译期预警内存泄漏风险。constexpr编译期计算:所有大小、对齐参数均在编译期完成计算,无运行时开销,极致优化性能。noexcept规范标注:对无异常抛出的函数明确标注,帮助编译器做更激进的优化。
四、使用示例
4.1 原始内存池使用
int main() {
// 定义16字节大小、8字节对齐的内存池
FixedMemoryPool<16, 8> pool;
// 分配内存
void* p1 = pool.allocate();
void* p2 = pool.allocate();
// 使用内存(示例:写入数据)
new (p1) int(123);
new (p2) double(3.14);
// 归还内存
pool.deallocate(p1);
pool.deallocate(p2);
return 0;
}4.2 对象池使用
#include <string>
#include <cstdio>
int main() {
// 定义std::string对象池,每个块512个对象
ObjectPool<std::string, 512> str_pool;
// 构造对象,支持任意构造函数
std::string* s1 = str_pool.construct("Hello, C++17 Memory Pool!");
std::string* s2 = str_pool.construct(10, 'a');
// 使用对象
printf("%s\n", s1->c_str());
printf("%s\n", s2->c_str());
// 销毁对象(自动调用析构函数,归还内存)
str_pool.destroy(s1);
str_pool.destroy(s2);
return 0;
}4.3 线程安全版本多线程使用
#include <thread>
#include <vector>
int main() {
// 64字节、Cache Line对齐的线程安全内存池
ThreadSafeFixedMemoryPool<64, 64> pool;
constexpr int thread_count = 8;
constexpr int alloc_per_thread = 10000;
std::vector<std::thread> threads;
threads.reserve(thread_count);
// 多线程并发分配与释放
for (int i = 0; i < thread_count; ++i) {
threads.emplace_back([&]() {
std::vector<void*> ptrs;
ptrs.reserve(alloc_per_thread);
// 批量分配
for (int j = 0; j < alloc_per_thread; ++j) {
ptrs.push_back(pool.allocate());
}
// 批量释放
for (void* p : ptrs) {
pool.deallocate(p);
}
});
}
// 等待所有线程结束
for (auto& t : threads) {
t.join();
}
// 验证:所有内存已归还
printf("Free slots: %zu\n", pool.free_slots());
printf("Total slots: %zu\n", pool.total_slots());
return 0;
}五、关键注意事项
- 生命周期管理:内存池的生命周期必须长于所有从它分配的对象,否则会出现野指针和未定义行为。
- 对象析构:使用
ObjectPool时,必须手动调用destroy销毁对象,否则对象的析构函数不会执行,会导致资源泄漏。 - 固定大小限制:本实现为固定大小内存池,仅能分配指定大小的内存;如需可变大小分配,可基于多个不同槽大小的固定内存池实现slab分配器。
- 性能优化:高并发场景下,可基于线程局部存储(TLS)实现无锁内存池,每个线程持有独立的内存池,减少锁竞争。
- 标准库优先:C++17标准库已提供成熟的内存池组件,生产环境优先使用标准实现,避免重复造轮子。
六、C++17 标准库内置内存池(PMR)
C++17引入了多态内存资源(PMR),在<memory_resource>头文件中提供了开箱即用的内存池实现,经过高度优化,兼容STL容器,生产环境优先使用:
#include <memory_resource>
#include <vector>
#include <string>
int main() {
// 1. 线程安全的池化内存资源(多线程场景)
std::pmr::synchronized_pool_resource sync_pool;
// 2. 单线程无锁池化内存资源(单线程场景,性能更高)
std::pmr::unsynchronized_pool_resource unsync_pool;
// 3. 单调递增内存池(适合频繁分配、无需单独释放的场景,性能极致)
std::pmr::monotonic_buffer_resource mono_pool;
// 绑定内存池到STL容器
std::pmr::vector<int> vec(&sync_pool);
vec.reserve(1000);
for (int i = 0; i < 1000; ++i) {
vec.push_back(i);
}
std::pmr::string str("Hello, C++17 PMR!", &sync_pool);
// 无需手动释放,内存池析构时自动释放所有内存
return 0;
}