news 2026/8/29 7:35:03

Qt C++11 智能指针详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qt C++11 智能指针详解

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(); // 2

make_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>

✅ 用std::unique_ptr

QSharedPointer<T>std::shared_ptr<T>

✅ 用std::shared_ptr

QWeakPointer<T>std::weak_ptr<T>

✅ 用std::weak_ptr

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_ptr

7. 常见陷阱

陷阱 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 高效

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 7:34:52

YOLOv8花卉识别实战:从环境搭建到模型训练全流程

又到了每年毕业设计冲刺的阶段&#xff0c;不少同学选定了“花卉图像识别”这个方向&#xff0c;但一打开 YOLOv8 的官方仓库&#xff0c;面对一大堆文件和命令行&#xff0c;还是不知道该从哪里下手。本文就围绕YOLOv8 PyTorch这一套主流组合&#xff0c;用一个完整的花卉识别…

作者头像 李华
网站建设 2026/8/29 7:34:35

基于经验反馈的匹配权重学习机制及其在动态认知系统中的应用

基于经验反馈的匹配权重学习机制及其在动态认知系统中的应用作者&#xff1a; 东塬一老翁技术&#xff1a;多模态智能技术研发工作室---摘要在动态认知系统中&#xff0c;个体如何利用过往经验调整未来认知策略是理解智能适应性的核心问题。本文在前期认知模型的基础上&#xf…

作者头像 李华
网站建设 2026/8/29 7:28:16

阿里十面面试经历复盘:从技术深挖到系统设计全流程解析

1. 十面不是运气差&#xff0c;是流程叠加的真实面貌收到意向书那天&#xff0c;手机弹邮件的瞬间我盯着屏幕愣了好几秒&#xff0c;没有想象中的狂喜&#xff0c;反而是一种“终于结束了”的虚脱感。整理了下时间线&#xff0c;从简历投出去到Offer落袋&#xff0c;前前后后两…

作者头像 李华
网站建设 2026/8/29 7:27:58

大厂运维开发笔试解析:从滴滴真题看考察逻辑与备考思路

先交代一下背景&#xff1a;我当年正好也在刷大厂运维开发的校招题&#xff0c;看到滴滴这套2018年的笔试题时&#xff0c;第一感觉是“这岗位比想象中有分量”。很多人以为运维开发就是“会点脚本的运维”&#xff0c;但看完这套题你会发现&#xff0c;它实际上在筛一类人——…

作者头像 李华
网站建设 2026/8/29 7:26:31

STEAM: A Semantic-Level Knowledge Editing Framework for Large Language Models

该文章提出了STEAM语义级知识编辑框架,解决现有大语言模型知识编辑中语义连贯性不足的问题,通过潜在空间定位与对齐,让更新知识更好融入模型原有知识结构,提升推理能力与语义一致性。 一、文章主要内容 现有知识编辑方法的局限 大语言模型(LLMs)经大规模预训练存储大量事…

作者头像 李华
网站建设 2026/8/29 7:25:27

NXP Trimension UWB方案:从原理到工程实践,解析无人机精准降落引导

NXP Trimension超宽带&#xff08;Ultra-Wideband&#xff09;方案支撑Jedsy X医疗配送无人机实现精准降落引导&#xff0c;这个案例我关注了挺久。医疗无人机真正难的不是巡航那几公里&#xff0c;而是最后三到五米——你要让一架带着血液样本或急救药品的飞机&#xff0c;稳稳…

作者头像 李华