C++11 智能指针详解
1. 核心概念:RAII
智能指针基于RAII(Resource Acquisition Is Initialization),把堆内存的生命周期绑定到栈对象,自动释放,避免 new/delete 配对失误与内存泄漏。
C++11 引入三种智能指针(位于 ):
指针 | 所有权 | 开销 | 用途 |
|---|---|---|---|
std::unique_ptr<T> | 独占 | 零开销 | 默认首选,单一所有者 |
std::shared_ptr<T> | 共享 | 原子引用计数 | 多个所有者共享 |
std::weak_ptr<T> | 不持有 | 弱引用 | 打破循环引用,观察者 |
2. std::unique_ptr — 独占所有权
零开销抽象,大小等同裸指针(默认情况下),推荐默认使用。
#include <memory> // 创建方式 1:直接构造 std::unique_ptr<Sensor> p1(new Sensor(0x01)); // 创建方式 2(推荐):std::make_unique(C++14,5.14.2 MSVC2017 支持) auto p2 = std::make_unique<Sensor>(0x02); // 解引用 p2->readData(); (*p2).reset(); // 所有权转移(只能移动,不能拷贝) auto p3 = std::move(p2); // p2 变 nullptr // auto p4 = p3; // ❌ 编译错误:禁止拷贝 // 释放并取得裸指针(之后必须手动 delete) Sensor *raw = p3.release(); // 重置 p3.reset(); // 析构并置空 p3.reset(new Sensor(0x03));数组版本
auto arr = std::make_unique<double[]>(64); // 64 个 double arr[0] = 1.0; arr[1] = 2.0;自定义删除器
// 管理文件句柄 std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("data.csv", "r"), &fclose); // Qt 对象使用 deleteLater 避免信号槽崩溃 auto widget = std::unique_ptr<QWidget, void(*)(QWidget*)>( new QWidget, [](QWidget *w) { w->deleteLater(); } );3. std::shared_ptr — 共享所有权
引用计数管理,多个 shared_ptr 可指向同一对象,最后一个析构时释放。
// 创建方式 1 std::shared_ptr<Sensor> p1(new Sensor(0x01)); // 创建方式 2(推荐):std::make_shared // 一次性分配对象 + 控制块,省一次内存分配 auto p2 = std::make_shared<Sensor>(0x02); // 共享:引用计数 +1 auto p3 = p2; qDebug() << p3.use_count(); // 2 // 拷贝 auto p4 = p3; // 引用计数 3 qDebug() << p4.use_count(); // 3 // 重置:引用计数 -1,归 0 时析构 p4.reset(); qDebug() << p3.use_count(); // 2make_shared vs 直接 new
// 推荐:一次分配,缓存友好 auto p = std::make_shared<Sensor>(0x01); // 不推荐:两次分配(对象 + 控制块),且无 weak_ptr 安全问题 std::shared_ptr<Sensor> p(new Sensor(0x01));自定义删除器
// 数组(C++17 才支持 shared_ptr<T[]>,C++11 用删除器) std::shared_ptr<double> arr( new double[64], std::default_delete<double[]>() ); // Qt 对象 auto btn = std::shared_ptr<QPushButton>( new QPushButton("OK"), [](QPushButton *b) { b->deleteLater(); } );4. std::weak_ptr — 不持有所有权
只观察 shared_ptr 管理的对象,不增加引用计数,用于打破循环引用。
auto sp = std::make_shared<Sensor>(0x01); std::weak_ptr<Sensor> wp = sp; // 不增加计数 // 安全访问:lock() 返回 shared_ptr,对象已死则返回空 if (auto locked = wp.lock()) { locked->readData(); // 安全使用 } else { qDebug() << "对象已被销毁"; } // 检查是否过期 if (wp.expired()) { ... } // 使用计数 qDebug() << wp.use_count();经典场景:打破循环引用
class Node { public: std::shared_ptr<Node> next; std::weak_ptr<Node> prev; // ❌ 若用 shared_ptr,环上对象永不释放 }; // 父子关系:父强持有子,子弱引用父 class Parent; class Child { public: std::weak_ptr<Parent> parent; // 弱引用父 }; class Parent { public: std::vector<std::shared_ptr<Child>> children; // 强持有子 };5. Qt 智能指针 vs STL 智能指针
Qt 自带智能指针,但C++11 起官方推荐用 STL 版本(Qt 文档已明确):
Qt 指针 | STL 等价 | 建议 |
|---|---|---|
QScopedPointer<T> | std::unique_ptr<T> | ✅ 用 |
QSharedPointer<T> | std::shared_ptr<T> | ✅ 用 |
QWeakPointer<T> | std::weak_ptr<T> | ✅ 用 |
QPointer<T> | 无直接等价(仅限 QObject) | ✅ Qt 专用,保留 |
QPointer — Qt 特有
只能用于 QObject 派生类,被delete时自动置空,但本身不管理生命周期:
QPushButton *raw = new QPushButton; QPointer<QPushButton> p = raw; delete raw; // p 现在自动为 nullptr,不会悬空 if (p) { ... } // 安全检查// 实战:信号槽中保存对方指针 QPointer<Sensor> m_sensor; // 成员变量 void onTimeout() { if (!m_sensor) return; // 对方可能已被销毁 m_sensor->readData(); }6. 实战选择决策树
是否管理 QObject(有 parent 机制)? ├─ 是 → 让 parent 接管,或用 QPointer<T> 当观察者 └─ 否 → 需要共享吗? ├─ 否 → std::unique_ptr<T> (首选) ├─ 是 → std::shared_ptr<T> (多所有者) └─ 出现循环引用 → 配合 std::weak_ptr7. 常见陷阱
陷阱 1:用裸指针构造多个 shared_ptr → 双重释放
Sensor *raw = new Sensor(0x01); std::shared_ptr<Sensor> p1(raw); std::shared_ptr<Sensor> p2(raw); // ❌ 两个独立控制块,双重释放 // ✅ auto p1 = std::make_shared<Sensor>(0x01); // auto p2 = p1;陷阱 2:循环引用 → 内存泄漏
class A { std::shared_ptr<B> b; // A 持 B }; class B { std::shared_ptr<A> a; // B 持 A → 永不释放 }; // ✅ 其中一个改用 std::weak_ptr陷阱 3:this 直接交给 shared_ptr
class Widget { void registerSelf() { // ❌ 把 this 包装成 shared_ptr,外部析构时本对象被双重销毁 manager.registerObj(std::shared_ptr<Widget>(this)); } }; // ✅ 让类继承 std::enable_shared_from_this class Widget : public std::enable_shared_from_this<Widget> { void registerSelf() { manager.registerObj(shared_from_this()); } }; // 前提:Widget 必须已经由 shared_ptr 管理陷阱 4:make_shared 与 weak_ptr 共存的延迟释放
struct Big { char data[1024 * 1024]; // 1MB }; { auto sp = std::make_shared<Big>(); // 对象+控制块同一块内存 std::weak_ptr<Big> wp = sp; sp.reset(); // 引用计数归 0,但控制块还在(wp 没死) // 此时 Big 对象内存未释放!直到 wp 析构 } // ✅ 若 Big 很大且 wp 长寿,应分开 std::shared_ptr<Big>(new Big)陷阱 5:智能指针管理数组却当单对象用
std::unique_ptr<Sensor> p(new Sensor[10]); // ❌ 调用 delete 而非 delete[] // ✅ std::unique_ptr<Sensor[]> p(new Sensor[10]); // 或 auto p = std::make_unique<Sensor[]>(10);8. Qt 项目实战示例
class SensorManager { public: void addSensor(quint8 id) { // 独占持有,使用 deleteLater 删除器 auto sensor = std::unique_ptr<Sensor, void(*)(Sensor*)>( new Sensor(id), [](Sensor *s) { s->deleteLater(); } ); m_sensors.push_back(std::move(sensor)); } std::shared_ptr<PressureBuffer> getBuffer() { // 缓存:返回 shared_ptr,多个视图共享 if (!m_buffer) { m_buffer = std::make_shared<PressureBuffer>(); } return m_buffer; } // 信号槽中安全持有对方 QPointer<QWidget> m_viewWidget; std::vector<std::unique_ptr<Sensor, void(*)(Sensor*)>> m_sensors; std::shared_ptr<PressureBuffer> m_buffer; };9. 核心原则速记
默认选 std::unique_ptr
(零开销、表达力强)
需要共享才用 std::shared_ptr
(避免滥用导致引用计数遍布)
优先 std::make_unique / std::make_shared
(少一次分配、异常安全)
循环引用必配 std::weak_ptr
QObject 优先让 parent 管理生命周期
,跨线程观察用 QPointer
不要用裸指针初始化多个智能指针
自定义删除器配合 Qt deleteLater,避免信号槽中悬空
移动语义优于拷贝
:auto p2 = std::move(p1) 比 auto p2 = p1 高效