标题
C++ 自定义内存池优化点云处理:从裸指针到 RAII 自动回收
摘要
实现一个基于 std::shared_ptr 自定义 deleter 的点云内存池,解决裸指针手动 release 容易泄漏的问题。采用预分配 + 空闲队列 + 引用计数 + 自动清零归还,适合激光雷达、点云处理、感知管线等高频分配场景。
关键词
C++;内存池;对象池;shared_ptr;自定义 deleter;点云;RAII;std::queue
一、问题背景
点云处理中,每帧都要申请和释放大量数据。
如果每次都用 new / delete,会带来频繁堆分配、内存碎片和额外锁竞争。
原始版本使用裸指针和手动 release,调用方一旦忘记归还,就会造成内存泄漏。
同时,空闲队列、析构函数和容器类型不匹配,也容易引发编译错误和未定义行为。
二、改进思路
核心目标有三点:
第一,预分配内存,减少运行时分配开销;
第二,用空闲队列管理可用块,实现 O(1) 获取和归还;
第三,用 std::shared_ptr 自定义 deleter 替代手动 release,离开作用域自动归还。
三、关键设计
| 模块 | 作用 | 实现方式 |
|---|---|---|
PointXYZ |
替代 vector<float> |
语义更清晰,便于后续对齐 |
Block |
内存池基本单元 | 包含数据向量和 std::atomic<int> 引用计数 |
pool_ |
所有权容器 | vector<shared_ptr<Block>> 负责最终释放 |
freeList_ |
空闲块队列 | queue<shared_ptr<Block>> 管理可用块 |
acquire |
获取内存 | 从队列取出并返回带自定义 deleter 的 shared_ptr |
| deleter | 自动归还 | 清零数据、引用计数减一、归零后放回队列 |
available |
监控容量 | 加锁返回空闲队列大小 |
四、完整代码
cpp
#include <iostream>
#include <vector>
#include <queue>
#include <mutex>
#include <memory>
#include <algorithm>
#include <atomic>
// 点云基本结构,替代原来的 vector<float>
// 每个点有 xyz 三个坐标,语义清晰
struct PointXYZ {
float x = 0.0f, y = 0.0f, z = 0.0f;
};
class PointCloudPool : public std::enable_shared_from_this<PointCloudPool> {
private:
// 内存池里的一块,包含实际点云数据和引用计数
struct Block {
std::vector<PointXYZ> data;
std::atomic<int> ref{0}; // 0=空闲,>=1=正在被使用
};
// pool_ 持有所有 Block,负责最终释放内存
std::vector<std::shared_ptr<Block>> pool_;
// freeList_ 存放当前空闲可用的 Block (使用 std::queue)
std::queue<std::shared_ptr<Block>> freeList_;
mutable std::mutex mtx_; // 加 mutable 允许在 const 方法中加锁
size_t capacity_;
public:
// 1. 构造函数:启动时一次性分配 count 块内存,全部塞进 freeList_
PointCloudPool(size_t count, size_t pointsPerCloud) : capacity_(count) {
for (size_t i = 0; i < count; ++i) {
auto blk = std::make_shared<Block>();
blk->data.resize(pointsPerCloud);
pool_.push_back(blk);
freeList_.push(blk);
}
std::cout << "内存池初始化完成,预分配 " << count << " 块内存" << std::endl;
}
// 2. 析构函数:直接清空 pool_,所有 Block 会被自动安全释放
// 删掉了会导致报错的 freeList_.clear()
~PointCloudPool() {
std::lock_guard<std::mutex> lock(mtx_);
pool_.clear();
}
// 3. 获取内存:返回 shared_ptr,利用自定义 deleter 自动归还
std::shared_ptr<std::vector<PointXYZ>> acquire() {
std::lock_guard<std::mutex> lock(mtx_);
if (freeList_.empty()) return nullptr; // 池空了返回空指针
auto blk = freeList_.front();
freeList_.pop();
blk->ref.store(1); // 标记为已占用
auto self = shared_from_this();
auto raw = &blk->data;
// 自定义 shared_ptr,deleter 在对象离开作用域时自动触发
return std::shared_ptr<std::vector<PointXYZ>>(
raw,
blk, self {
// 归还时先清零,防止下一帧拿到脏数据
std::fill(blk->data.begin(), blk->data.end(), PointXYZ{});
// 引用计数减 1,如果降为 0 就放回空闲队列
if (blk->ref.fetch_sub(1) == 1) {
std::lock_guard<std::mutex> lk(self->mtx_);
self->freeList_.push(blk);
}
});
}
// 4. 查询当前还有多少块空闲内存
size_t available() const {
std::lock_guard<std::mutex> lock(mtx_);
return freeList_.size();
}
};
int main() {
// 创建内存池:10 块,每块 10 万个点
auto pool = std::make_shared<PointCloudPool>(10, 100000);
// 检查初始状态
std::cout << "初始化空闲块数: " << pool->available() << std::endl;
// 获取一块内存
auto cloud = pool->acquire();
if (cloud) {
std::cout << "获取点云成功,点数: " << cloud->size()
<< ",剩余空闲: " << pool->available() << std::endl;
// 写入测试数据
(*cloud)[0] = {1.0f, 2.0f, 3.0f};
}
// cloud 离开作用域,deleter 自动触发:清零 + 归还
// 检查归还后的状态
std::cout << "归还后空闲块数: " << pool->available() << std::endl;
return 0;
}
五、踩坑记录
| 报错现象 | 原因 | 修复方式 |
|---|---|---|
queue 没有 clear() |
std::queue 不提供 clear 接口 |
析构时改为 pool_.clear() |
vector 没有 push() |
push 是 queue 的接口 |
freeList_ 统一使用 std::queue |
vector 没有 front() |
原定义与使用方式不匹配 | 改用 queue 的 front() / pop() |
| 初始化后空闲数异常 | 缺少构造函数或类型不匹配 | 补全构造函数,统一容器类型 |
| 多线程访问不安全 | 空闲队列被并发修改 | acquire、available、析构都加锁 |
六、运行结果
程序输出大致如下:
text
内存池初始化完成,预分配 10 块内存
初始化空闲块数: 10
获取点云成功,点数: 100000,剩余空闲: 9
归还后空闲块数: 10
七、适用场景
该方案适合点云帧缓存、传感器回调缓冲、仿真数据复用等固定大小对象的高频分配场景。
如果进入高频多线程管线,可以进一步替换为无锁队列、线程本地缓存或 std::pmr 分配器,降低锁竞争。