恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

《MC++》学习–智能指针

  • 首页
  • 资讯中心
  • /
  • 《MC++》学习–智能指针

相关资讯

foobox-cn 使用教程:为 foobar2000 换一套现代 DUI 皮肤的完整上手指南 2026/10/15 20:36:44
Java+Vue酒店管理系统全栈开发实战 2026/10/15 20:36:44
2024年五大技术趋势预测与工程实践指南 2026/10/15 20:36:44

最新资讯

32位程序突破2GB内存限制的原理与实践
AI 编程工具尝鲜之 Cursor 新模型 sonic 体验:把 Base URL 改到 TaoToken 的实测记录
gpmall商城系统部署指南:从解压到前后端联调的完整落地流程
求职迷茫破解指南:从定位到面试的实战策略
Ubuntu 24.04 安装 PostgreSQL 18:PGDG 源、远程连接与性能调优实操
滞销款不是等到季末才发现:新品上市4周,鞋服品牌该看什么?

今日推荐

AI视频生成的时间一致性难题与时空建模演进
基于bbox的PDF文档解析:多栏重排与水印过滤实战
探矿数据进RAG前的清洗全攻略:TXT、Word、PDF、网页四类文档处理实践

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

《MC++》学习–智能指针

发布时间:2026/10/15 20:36:44
《MC++》学习–智能指针 《MC》学习–智能指针1. 为什么需要智能指针1.1 传统动态内存管理的问题传统的内存管理中我们一般使用new/delete和malloc/free但当程序发生异常时是有可能发生内存泄漏的比如我有一个process()函数如下void process() { int* ptr new int(42); throw std::runtime_error(发生异常); delete ptr; // 无法执行 }调用这个函数时new int(42)会动态创建一个int对象将它初始化为 42并把地址赋给ptr。接着我们人为抛出std::runtime_error异常程序开始寻找匹配的异常处理程序后面的delete ptr就不会执行。这里假设外层有匹配的catch。异常向外传播时会发生栈展开Stack Unwinding也就是逐层退出函数调用并销毁已经构造完成的局部对象。局部变量ptr的生命周期虽然结束了但它只是一个裸指针不会自动释放指向的动态内存因此这里会出现内存泄漏。1.2 RAII 是啥RAII 全称是 Resource Acquisition Is Initialization资源获取即初始化。它的核心思想是通过对象的构造与析构管理资源使资源的生命周期与管理它的对象生命周期绑定。结合下面的例子我的简单理解是这里的局部智能指针离开作用域时会自动调用析构函数释放它管理的资源不需要我们手动调用delete。那么智能指针如何践行RAII的思想呢我们来看一个简单的例子同样的process()函数我们使用智能指针来申请空间void process() { auto ptr std::make_uniqueint(42); throw std::runtime_error(发生异常); }首先ptr 是一个 std::unique_ptr 对象当程序同样走到throw std::runtime_error(“发生异常”)同样程序开始寻找异常处理程序栈展开过程中会调用 ptr 的析构函数析构函数就会自己完成资源的清理。1.3 智能指针有更高的性能在《MC》的《Memory and Performance Overhead of Smart Pointers》作者说到std::unique_ptr has no memory or performance overhead compared to the explicit usage of new and delete. That is great because std::unique_ptr offers an excellent benefit by automatically managing the lifetime of its resource without any additional cost.里面一个比较关键的结论是与手动使用 new 和 delete 相比std::unique_ptr 不会带来任何内存或性能方面的开销。当然作者也提到std::shared_ptr没有那么高的效率根据作者的实验,std::shared_ptr 的效率大约只有 new 和 delete 操作的一半。就连 std::make_shared 的操作也会带来大约 10%的性能损耗。但作者也提到由于 std::shared_ptr 实际上是一种共享所有权的实现方式这意味着只有第一个 std::shared_ptr 需要承担性能和内存方面的开销。其余的 shared pointer 则共享了用于管理底层对象的基础设施。具体来说每个 std::shared_ptr 只需要进行一次内存分配即可。虽然作者的实验是在特定环境下但我觉得我们可以认为智能指针有着更高的性能。2. unique_ptr2.1 unique_ptr创建方式std::unique_ptrint p1(new int(42)); auto p2 std::make_uniqueint(42);这是常见的两种创建方式我个人比较常用第二种。std::make_unique从 C14 开始提供写起来更简洁也减少了显式使用new。上面第一种写法本身也是安全的它在同一条语句中将新建对象交给unique_ptr管理。2.2 独占所有权这里的独占所有权意思是只有一个所有者负责资源生命周期而不是只有一个指针能指向这块内存。当我有一个unique_ptr p1不可以用一个p2去复制p1但是可以通过std::move将p1管理的对象交给p2。2.3 auto_ptr的替代品《MC》中提到了auto_ptr并说明unique_ptr为auto_ptr的替代品原因是当你复制一个 std::auto_ptr 时其所指向的资源会被移动。也就是说看似是复制操作但实际上却是移动操作。这里借用下面一张图描述3. shared_ptr3.1 shared_ptr创建方式std::shared_ptrint p1(new int(42)); auto p2 std::make_sharedint(42);创建的方式与unique_ptr类似这里不多赘述。3.2 共享所有权这里稍微提一嘴 强引用 和 弱引用强引用指参与共享所有权会让被管理对象保持存活我们这里的 shared_ptr 就是强引用弱引用观察对象但不参与共享所有权也不会阻止对象被销毁。weak_ptr 持有弱引用。关于shared_ptr我觉得最重要的特性就是共享所有权。当你创建了一个管理对象的shared_ptr p1时可以通过auto p2 p1;这样的操作复制p1。在没有其他共享所有者的情况下p1.use_count()和p2.use_count()都返回 2表示有两个共享所有者get()返回的是保存的裸指针并不是引用计数。这点和unique_ptr有本质差异。最后一个共享所有者通过析构、reset()等方式放弃所有权后被管理对象才会销毁。因此不必显式对p1和p2都调用reset()它们离开作用域时的析构也会放弃所有权。3.3 shared_ptr对多线程的适配作者在《std::shared_ptr》结尾提到“std::shared_ptr has minimum multithreading guarantees”即在多线程中shared_ptr有最基本的保证我们可以参考一下例子#include iostream #include memory #include thread int main() { auto ptr std::make_sharedint(42); std::thread t1([p ptr]() { std::cout 线程1 *p \n; }); std::thread t2([p ptr]() { std::cout 线程2 *p \n; }); t1.join(); t2.join(); std::cout 引用计数 ptr.use_count() \n; }在这个例子中ptr 创建时共享所有者数量为 1。创建两个线程时分别复制 shared_ptr让线程各自持有一份所有权。每个线程执行期间它持有的 shared_ptr 保证被管理对象不会因为其他共享所有者释放所有权而提前销毁。两个线程结束后线程内部的 shared_ptr 被销毁。join() 返回之后ptr.use_count() 为 1。两个线程的输出顺序不确定而且由于它们同时向 std::cout 输出输出文本可能交错。关键在于不同的 shared_ptr 实例可以安全地在不同线程中修改各自的所有权状态即使它们共享同一个控制块。 引用计数的相关更新具有原子性保障可以防止多个线程同时释放所有权时破坏计数。shared_ptr 保证共享所有权管理的安全本例对被管理对象只有并发读取因此也没有数据竞争。如果存在并发读写或写写就需要额外同步。4. weak_ptr4.1 weak_ptr 创建方式前面讲到shared_ptr是强引用会参与对象的生命周期管理。如果我只是想知道这个对象还在不在需要的时候再访问它但不想因为自己持有一个指针就让对象一直存活这时候就可以使用weak_ptr。autosharedstd::make_sharedint(42);std::weak_ptrintweakshared;这里的weak观察的是shared管理的对象。创建weak_ptr不会增加强引用计数在这个例子中shared.use_count()仍然是 1。weak.use_count()也返回 1但这个值表示共享所有者的数量并不是有多少个weak_ptr。这也呼应了前面说的弱引用可以保持观察关系但不负责让对象继续存活。4.2 怎么理解“临时性所有权”《MC》的《std::weak_ptr》中用“temporary ownership”描述这种关系。我觉得这个说法适合帮助理解但需要说明一点weak_ptr本身没有对象的所有权真正临时持有对象的是成功调用lock()后得到的shared_ptr。weak_ptr不能像前两种指针一样直接使用*或-访问对象需要先调用lock()。如果对象还存在就得到一个共享所有者如果已经过期就得到一个空的shared_ptr。我们可以看下面这个片段需要包含memory和iostreamautosharedstd::make_sharedint(42);std::weak_ptrintweakshared;if(autotemporaryweak.lock()){std::cout*temporary\n;std::couttemporary.use_count()\n;// 此时有两个共享所有者}// temporary 析构剩下 shared 继续管理对象shared.reset();// 最后一个共享所有者放弃所有权int 对象被销毁if(autotemporaryweak.lock()){std::cout*temporary\n;}else{std::cout对象已经不存在了\n;}这里第一次lock()成功后temporary会在持有期间保证对象继续存活。等它离开作用域这份临时的共享所有权就结束了。4.3 用 weak_ptr 打破循环引用《MC》在 std::weak_ptr 这一篇里还讲到了循环引用。比如对象 A 内部用shared_ptr持有 BB 内部又用shared_ptr持有 A。即使外部的共享所有者都释放了A 和 B 仍然相互持有强引用计数无法自动归零对象也就无法自动销毁。对于这个简单的双向引用可以把其中只需要观察对方的那一端改成weak_ptr。弱引用不增加强引用计数因此可以打破这个共享所有权的环。需要访问对方时再调用lock()检查是否成功即可。5. 综合实验所有权与对象生命周期下面用一个 C17 小实验观察同一个对象从独占管理到共享管理再到最后被销毁的过程。5.1 实验代码#includeiostream#includememory#includeutility// 用对象编号和析构日志观察资源的生命周期。classResource{public:explicitResource(intid):id_(id){std::coutconstruct Resource id_\n;}~Resource(){std::coutdestroy Resource id_\n;}Resource(constResource)delete;Resourceoperator(constResource)delete;intid()const{returnid_;}private:intid_;};intmain(){// 步骤 1用 make_unique 创建一个 Resource保存 get() 返回的地址。// 用 std::move 交给 unique2检查 unique1 为空、打印地址以便比较。// 后续步骤继续使用同一个 Resource观察它完整的生命周期。autounique1std::make_uniqueResource(0);Resource*addr1unique1.get();std::coutaddr1:addr1\n;autounique2std::move(unique1);(unique1nullptr)?std::coutunique1 is nullptr\n:std::coutunique1 is not nullptr\n;std::coutaddr2:unique2.get()\n;// 步骤 2用 shared_ptrResource shared1(std::move(unique2)) 接管。// 检查 unique2 为空、shared1.get() 地址未变记录 shared1.use_count()。// 复制 shared1 得到 shared2记录强引用计数。std::shared_ptrResourceshared1(std::move(unique2));(unique2nullptr)?std::coutunique2 is nullptr\n:std::coutunique2 is not nullptr\n;(shared1.get()addr1)?std::coutsame resource address\n:std::coutaddress changed\n;std::coutThe number of strong ref is:shared1.use_count()\n;autoshared2shared1;std::coutThe number of strong ref is:shared2.use_count()\n;// 步骤 3建立 weak_ptr再用 weak.lock() 获取临时 shared_ptr。// 成功后读取 use_count()离开 if 作用域后再次观察强引用计数。std::weak_ptrResourceweakshared1;std::coutThe number of strong ref is:shared2.use_count()\n;if(autotempweak.lock()){std::coutThe number of strong ref is:temp.use_count()\n;}std::coutThe number of strong ref is:shared2.use_count()\n;// 步骤 4逐个 reset 两个共享所有者在最后一次 reset 前后打印标记观察析构时机。// 确保 lock 返回的临时所有者已退出作用域再检查 weak.expired() 和 lock()。// 最后一个共享所有者退出后不再解引用最初保存的裸指针。shared1.reset();std::coutThe number of strong ref is:shared2.use_count()\n;std::coutbefore last reset\n;shared2.reset();std::coutafter last reset\n;std::coutThe number of strong ref is:weak.use_count()\n;std::coutweak expired: std::boolalphaweak.expired()\n;if(autotempweak.lock()){std::coutobject still exists\n;}else{std::coutobject is destroyed\n;}}5.2 运行输出使用 g 16.2.0以-stdc17 -g -O0 -Wall -Wextra -pedantic编译无编译诊断运行退出码为 0。本次实际输出如下地址每次运行可能不同也不必与调试截图一致。construct Resource 0 addr1:0x1eaf86b0ea0 unique1 is nullptr addr2:0x1eaf86b0ea0 unique2 is nullptr same resource address The number of strong ref is:1 The number of strong ref is:2 The number of strong ref is:2 The number of strong ref is:3 The number of strong ref is:2 The number of strong ref is:1 before last reset destroy Resource 0 after last reset The number of strong ref is:0 weak expired: true object is destroyed两次所有权转移没有创建新的Resource对象地址保持相同。强引用计数依次为1 → 2 → 2 → 3 → 2 → 1 → 0复制shared_ptr会增加计数建立weak_ptr不会lock()成功后临时增加一次临时指针离开作用域后再减回去。5.3 调试观察释放前此时shared2.reset()还没执行shared1已为空shared2是最后一个共享所有者use count 1表示强引用计数为 1调试器显示的weak count 1表示还有一个弱引用。释放后执行这一行后强引用计数归零Resource被销毁weak显示expired。弱引用仍在不代表对象仍然存活。此时控制块还保留但它不能让对象继续存活后续lock()返回空指针程序输出object is destroyed。图中旧地址仍然可见id_显示成8306384这只是调试器展示的失效内存内容不是有效的对象值。地址仍然可见不代表可以继续解引用。此时addr1已是悬空指针。另外weak下的get()是调试器的展示weak_ptr本身没有这个接口weak.use_count()返回的仍是强引用计数。5.4 实验结论这个实验里对象只构造和析构各一次。unique_ptr通过移动转交独占所有权shared_ptr通过引用计数共享所有权最后一个共享所有者释放时对象销毁。weak_ptr只保留观察关系只有成功lock()得到的shared_ptr才能临时持有对象。6. 总结和一点碎碎念6.1 总结特性std::unique_ptrstd::shared_ptrstd::weak_ptr所有权独占共享不拥有对象能否复制否是复制后共享所有权是复制观察关系能否移动是转移独占所有权是转移已有的共享所有权是转移观察关系引用计数不使用引用计数参与强引用计数参与弱引用管理不增加强引用计数对对象生命周期的影响持有期间负责管理对象仍有共享所有者时对象保持存活不阻止对象销毁但可以保持控制块存活访问对象*、-非空时*、-指向有效对象时先lock()成功后通过得到的shared_ptr访问典型用途由一个所有者管理资源需要时转交多个所有者共同管理资源观察共享资源、打破共享所有权的循环引用管理开销通常较低无共享控制块大小受删除器影响有控制块及引用计数维护成本复用共享控制块有弱引用计数维护成本我的选择思路是先考虑unique_ptr确实需要多个所有者共同管理时再用shared_ptr需要观察共享对象但不想延长它的生命周期时用weak_ptr。另外weak.use_count()返回的是强引用计数不是弱引用数量。6.2 一点无关紧要的话这是我第一篇 C 学习笔记。说实话作为一名学了两年嵌入式软件的大三学生我感觉现在再来补一些 C 应用有点晚但是还是必要的。这篇笔记不知道能有多少人看到但我希望能给读到这里的人带来一点点帮助。也欢迎大家在评论区给我留下一点建议不管是文章内容上还是其他方面。大概就这样吧。参考资料Rainer GrimmMemory and Performance Overhead of Smart PointersRainer Grimmstd::unique_ptrRainer Grimmstd::shared_ptrRainer Grimmstd::weak_ptr

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号