欢迎光临
我们一直在努力

第二阶段 · 第 2 课:C++ 智能指针

第二阶段 · 第 2 课:C++ 智能指针
上一课我们学习了 RAII:让资源跟随对象生命周期自动释放。

这一课学习三种核心智能指针:

– std::unique_ptr:独占所有权
– std::shared_ptr:共享所有权
– std::weak_ptr:观察对象,不增加强引用计数

结合你平时的 Qt/C++ 项目,我们重点讨论动态对象、相机状态对象、线程共享数据,以及如何避免内存泄漏和悬空指针。
一、为什么需要智能指针?
传统写法:

void test()
{
Camera* camera = new Camera();

camera->start();

delete camera;
}

这种写法容易出现几个问题:

  • 中途 return,忘记 delete。
  • 多个分支都释放同一个对象,造成重复释放。
  • 对象释放后,其他地方还在使用它,造成悬空指针。
    智能指针将对象的释放规则封装起来,减少这些风险。
    二、std::unique_ptr:独占所有权
  • 一个智能指针

    独占一个对象的所有权,离开生命周期时自动释放。

  • 基本用法
  • #include <memory>

    auto camera = std::make_unique<Camera>();

    camera->start();

    当 camera 离开作用域时,对象会自动销毁,不需要写 delete。
    2. 不能直接复制
    auto camera1 = std::make_unique();

    // auto camera2 = camera1; // 编译错误

    因为独占所有权不允许两个 unique_ptr 同时拥有同一个对象。

    如果要转移所有权,可以使用 std::move:
    auto camera1 = std::make_unique();
    auto camera2 = std::move(camera1);

    此时:

    • camera2 拥有对象。
    • camera1 变为空指针。
    • 对象不会因为转移而重新创建。
  • 什么时候使用?
    适合一个明确的对象所有者,例如:
  • class CameraManager
    {
    public:
    CameraManager()
    : m_camera(std::make_unique<Camera>())
    {
    }

    void start()
    {
    m_camera->start();
    }

    private:
    std::unique_ptr<Camera> m_camera;
    };

    CameraManager 独占相机对象,管理器销毁时,相机对象也会销毁。

    这通常是动态分配对象的优先选择。

    三、std::shared_ptr:共享所有权
    当多个对象确实需要共同拥有同一个对象时,可以考虑 shared_ptr。
    FormCamera

    CameraWorker

    同一个共享对象

    引用计数管理生命周期

  • 基本用法
  • auto camera = std::make_shared<Camera>();

    auto camera2 = camera;

    qDebug() << camera.use_count(); // 通常为 2

    这里两个 shared_ptr 共享同一个对象。

    当其中一个指针销毁时,对象仍然存活;当最后一个强引用释放时,对象才会销毁。
    2. 结合你的相机状态对象
    假设有一个状态对象,需要被采集线程和界面逻辑共同持有:
    std::shared_ptr cameraState =
    std::make_shared();

    如果两个模块都使用 shared_ptr 保存它,那么只要至少有一个强引用存在,对象就不会因为其他持有者释放引用而销毁。

    不过,这不意味着任何场景都应该使用 shared_ptr。

    使用前先问:这些模块真的共同拥有这个对象吗?

    如果对象实际上只由一个管理器拥有,其他模块只是借用它,那么 unique_ptr 加非拥有指针通常更清晰。

    另外,shared_ptr 的引用计数管理是线程安全的,但它不会自动保证对象内部成员的并发访问安全。

    四、std::weak_ptr:观察对象,不延长生命周期
    shared_ptr 有一个常见问题:循环引用。

    例如 A 持有 B,B 又持有 A:
    class B;

    class A
    {
    public:
    std::shared_ptr<B> b;
    };

    class B
    {
    public:
    std::shared_ptr<A> a;
    };

    如果两个对象互相持有 shared_ptr,即使外部释放了它们,引用计数也可能无法降到零,导致内存泄漏。

    解决方法之一是让其中一方使用 weak_ptr。

    class B;

    class A
    {
    public:
    std::shared_ptr<B> b;
    };

    class B
    {
    public:
    std::weak_ptr<A> a;
    };

    weak_ptr 不拥有对象,也不会增加强引用计数。

    使用时,先通过 lock() 尝试取得一个临时的 shared_ptr:
    if (auto a = b->a.lock()) {
    // a 在当前作用域内保证对象存活
    a->doSomething();
    }

    如果对象已经销毁,lock() 会返回空的 shared_ptr,因此可以安全判断。

    注意:weak_ptr 主要用于观察由 shared_ptr 管理的对象,不能直接替代任意裸指针。

    五、三种智能指针对比
    特性unique_ptrshared_ptrweak_ptr
    所有权独占共享不拥有
    是否延长对象生命周期是是否
    是否可以复制指针不可以可以可以
    主要用途明确的单一所有者确实存在的共享所有权观察共享对象、打破循环引用
    典型风险所有权转移后旧指针为空生命周期不清晰、循环引用对象销毁后 lock() 失败

    我的建议是:优先考虑普通对象或 unique_ptr,确实需要共享所有权时再使用 shared_ptr,需要观察共享对象时再使用 weak_ptr。

    六、Qt 项目里要特别注意什么?
    Qt 本身有一套对象生命周期管理机制:QObject 的父子对象关系。

    例如:
    auto* worker = new QObject(this);

    如果 this 是 QObject,那么父对象销毁时会自动销毁这个子对象。

    因此,对于有明确父对象的 Qt 对象,通常不需要再用 shared_ptr 管理同一个对象的生命周期。

    尤其不要随意混用:
    auto* object = new QObject(parent);

    // 不推荐:又把同一个对象交给 shared_ptr 管理
    std::shared_ptr ptr(object);

    这可能导致父对象和智能指针分别尝试销毁同一个对象,造成重复释放。

    对于 QObject,建议遵循以下规则:

    • 有父对象的 Qt 子对象:优先使用 Qt 父子生命周期管理。
    • 没有父对象、由 C++ 类独占的普通对象:考虑 unique_ptr。
    • 多个模块确实需要共同拥有的非 QObject 资源:考虑 shared_ptr。
    • Qt 跨线程通信:明确对象所属线程、析构时机和信号槽连接方式,智能指针不能代替线程设计。
      例如,你的相机采集线程不应该仅仅因为使用了 shared_ptr,就假设线程结束后所有相机资源都已安全关闭。线程停止、设备释放和对象析构仍然需要明确的顺序。
      七、结合你的代码:怎么选?
      假设有下面三种需求:

    场景 A:管理器独占相机状态对象
    std::unique_ptr<CameraState> m_state;

    适合对象由管理器独占管理,生命周期清晰的情况。

    场景 B:多个模块共同持有一份非 QObject 状态数据
    std::shared_ptr<CameraState> state;

    只有当多个模块都需要参与所有权管理时才考虑。

    场景 C:某模块只想观察对象是否还存在
    std::weak_ptr<CameraState> state;

    访问前调用 lock(),确认对象仍然存在。

    八、课后练习
    问题:CameraManager 独占一个相机对象,其他代码不需要共同拥有它。哪种方案最合适?

    A. std::shared_ptr,因为共享指针最安全

    B. std::unique_ptr,由管理器独占并管理生命周期

    C. 所有模块各自 new 一个相机对象

    D. 使用 std::weak_ptr 创建并拥有相机对象

    提交答案
    下一课我们学习移动语义与 std::move,理解智能指针如何转移所有权,以及如何避免大型图像数据和容器的不必要复制。

    赞(0)
    未经允许不得转载:171主机测评 » 第二阶段 · 第 2 课:C++ 智能指针
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址