4. 引用计数与智能指针:shared_ptr/weak_ptr原理、在HAL中的使用误区、循环引用导致泄漏
在Camera HAL开发中,C++智能指针是管理buffer、stream、request等动态对象生命周期的核心工具。然而,shared_ptr的循环引用是HAL内存泄漏的头号隐形杀手。本节从原理出发,剖析shared_ptr/weak_ptr在HAL中的典型误区和根治方法。
4.1 shared_ptr/weak_ptr核心原理
shared_ptr通过引用计数(Reference Count)实现共享所有权:每增加一个shared_ptr副本,计数+1;析构时计数-1,当计数归零时自动释放对象。weak_ptr则是一种“弱引用”,它不增加引用计数,只提供对对象的观察权,通过lock()获取临时shared_ptr。
| 特性 | shared_ptr | weak_ptr |
|---|---|---|
| 引用计数影响 | 增加计数,阻止对象释放 | 不增加计数,不阻止释放 |
| 访问对象 | 直接解引用(* / ->) | 需调用 lock() 返回 shared_ptr |
| 典型用途 | 共享所有权、资源生命周期管理 | 打破循环引用、缓存观察者 |
| 空悬风险 | 对象存活期间安全 | lock() 可能返回空(对象已释放) |
关键认知:weak_ptr 不延长对象生命周期,但提供“安全观察”能力。在 HAL 中,当需要“引用但不拥有”时(如回调上下文、观察者列表),必须使用 weak_ptr。
4.2 循环引用导致泄漏的经典模型
当两个对象互相持有对方的 shared_ptr 时,引用计数形成闭环,永远无法归零,导致内存泄漏。在 Camera HAL 中,这种场景常出现在 Stream ↔ BufferProducer 或 Request ↔ Callback 的双向依赖中。
4.3 HAL中的典型使用误区
以下是在Camera HAL代码评审中反复出现的三种循环引用模式:
- Stream与BufferManager双向持有:Stream内部保存BufferManager的shared_ptr,BufferManager的回调又持有Stream的shared_ptr,形成闭环。
- Request与ResultCallback互相引用:Request对象持有ResultCallback的shared_ptr,而回调闭包内又捕获了Request的shared_ptr。
- Pipeline与Stage的父子循环:Pipeline持有Stage的shared_ptr,Stage内部又通过回调持有Pipeline的shared_ptr。
⚠ 常见错误代码模式:在lambda回调中直接捕获this或shared_ptr by value,而未使用weak_ptr。例如:
mCallback = [this, sp = shared_from_this()](){ ... }; 如果this和sp形成闭环,则泄漏。
4.4 使用weak_ptr打破循环引用
解决方案是将循环链中的一方改为weak_ptr。通常选择“生命周期较短”或“被依赖方”使用weak_ptr。在HAL中,推荐将回调持有方或观察者侧改为weak_ptr。
// 错误:循环引用
class Stream {
std::shared_ptr<BufferProducer> mProducer;
};
class BufferProducer {
std::shared_ptr<Stream> mStream; // 形成闭环
};
// 正确:使用weak_ptr打破循环
class Stream {
std::shared_ptr<BufferProducer> mProducer;
};
class BufferProducer {
std::weak_ptr<Stream> mStream; // 弱引用,不增加计数
void useStream() {
if (auto sp = mStream.lock()) { // 安全访问
sp->doSomething();
}
}
};
4.5 循环引用检测与调试方法
在HAL开发中,检测循环引用泄漏需要结合静态分析与运行时工具:
| 方法 | 工具/手段 | 适用阶段 |
|---|---|---|
| 代码审查 | 检查所有 shared_ptr 成员变量,标记双向持有关系 | 开发阶段 |
| 引用计数日志 | 重写析构函数,打印对象销毁日志;对比创建/销毁次数 | 集成测试 |
| Valgrind / ASan | 检测堆内存泄漏,定位未释放对象 | 压力测试 |
| 自定义weak_ptr检查 | 在关键生命周期节点调用 weak_ptr::expired() 验证 | 单元测试 |
实战建议:在Camera HAL的Stream、Request、Pipeline等核心类中,强制规定:
• 所有“回调持有”必须使用 weak_ptr;
• 所有“跨模块引用”优先使用原始指针(配合生命周期契约)或 weak_ptr;
• 在析构函数中添加日志,确保每个对象都被正确释放。
• 所有“回调持有”必须使用 weak_ptr;
• 所有“跨模块引用”优先使用原始指针(配合生命周期契约)或 weak_ptr;
• 在析构函数中添加日志,确保每个对象都被正确释放。
4.6 最佳实践:HAL中的智能指针使用规范
- 所有权明确:每个对象只有一个明确的“所有者”使用 shared_ptr,其他观察者使用 weak_ptr 或原始指针。
- 避免裸 new:统一使用
std::make_shared创建对象,减少异常安全问题和内存碎片。 - 回调闭包捕获:在 lambda 中捕获
weak_from_this()而非shared_from_this(),并在回调入口处 lock() 检查。 - 定期审计:在代码评审中增加“循环引用检查” checklist,使用静态分析工具(如 clang-tidy)自动检测。
通过严格遵循上述规范,Camera HAL 中90%以上的智能指针泄漏问题可以在代码评审阶段被拦截。剩余10%则需要结合运行时检测工具和引用计数日志来定位。