第二阶段 · 第 2 课:C++ 智能指针
上一课我们学习了 RAII:让资源跟随对象生命周期自动释放。
这一课学习三种核心智能指针:
– std::unique_ptr:独占所有权
– std::shared_ptr:共享所有权
– std::weak_ptr:观察对象,不增加强引用计数
结合你平时的 Qt/C++ 项目,我们重点讨论动态对象、相机状态对象、线程共享数据,以及如何避免内存泄漏和悬空指针。
一、为什么需要智能指针?
传统写法:
void test()
{
Camera* camera = new Camera();
camera->start();
delete camera;
}
这种写法容易出现几个问题:
智能指针将对象的释放规则封装起来,减少这些风险。
二、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,理解智能指针如何转移所有权,以及如何避免大型图像数据和容器的不必要复制。



