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 的双向依赖中。

循环引用导致泄漏示意图 Stream shared_ptr<BufferProducer> BufferProducer shared_ptr<Stream> 持有 持有 引用计数闭环 Stream.ref_count = 1 (被BufferProducer持有) BufferProducer.ref_count = 1 (被Stream持有) 外部shared_ptr释放后,两者计数均无法归零 → 泄漏

4.3 HAL中的典型使用误区

以下是在Camera HAL代码评审中反复出现的三种循环引用模式:

  1. Stream与BufferManager双向持有:Stream内部保存BufferManager的shared_ptr,BufferManager的回调又持有Stream的shared_ptr,形成闭环。
  2. Request与ResultCallback互相引用:Request对象持有ResultCallback的shared_ptr,而回调闭包内又捕获了Request的shared_ptr。
  3. 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;
• 在析构函数中添加日志,确保每个对象都被正确释放。

4.6 最佳实践:HAL中的智能指针使用规范

  • 所有权明确:每个对象只有一个明确的“所有者”使用 shared_ptr,其他观察者使用 weak_ptr 或原始指针。
  • 避免裸 new:统一使用 std::make_shared 创建对象,减少异常安全问题和内存碎片。
  • 回调闭包捕获:在 lambda 中捕获 weak_from_this() 而非 shared_from_this(),并在回调入口处 lock() 检查。
  • 定期审计:在代码评审中增加“循环引用检查” checklist,使用静态分析工具(如 clang-tidy)自动检测。
HAL智能指针使用规范决策流程 是否拥有对象所有权? 使用 shared_ptr 使用 weak_ptr 检查要点 • 是否双向持有? • 回调是否捕获this? • 生命周期是否匹配? • 是否使用make_shared? • 析构日志是否完整?

通过严格遵循上述规范,Camera HAL 中90%以上的智能指针泄漏问题可以在代码评审阶段被拦截。剩余10%则需要结合运行时检测工具和引用计数日志来定位。