C++ 智能指针shared_ptr C++引用计数与自动内存管理【C++11】

发布时间 - 2026-01-26 00:00:00    点击率:
shared_ptr的引用计数是原子的,但仅保证同一shared_ptr对象的拷贝/赋值/析构线程安全;其指向资源的访问仍需手动同步,且循环引用需用weak_ptr破除。

shared_ptr 的引用计数不是原子的?别被默认假设骗了

默认情况下,shared_ptr 的引用计数操作是线程安全的——但仅限于对**同一个 shared_ptr 对象**的拷贝、赋值、析构。它不保护你指向的资源本身,也不保证多个 shared_ptr 指向同一块内存时的并发修改安全。

常见错误现象:std::shared_ptr p = std::make_shared(42); 然后在两个线程里分别执行 p.reset()p = nullptr; —— 这没问题;但如果一个线程在改 *p = 100;,另一个在 p.reset(),就可能触发未定义行为(UB),因为 *p 访问和析构竞争。

  • 引用计数内部使用原子操作(如 std::atomic),所以 shared_ptr 对象本身的生命周期管理是线程安全的
  • 但指向的对象(即 operator* / operator-> 访问的目标)完全不自动加锁
  • 若需多线程读写共享对象,仍要配合 std::mutexstd::atomic 显式同步

make_shared 比 new + shared_ptr(...) 更高效,但不能用于自定义删除器

std::make_shared 在一次内存分配中同时构造控制块和对象,而 shared_ptr 构造函数配合 new 需要两次分配(一次给对象,一次给控制块),性能差异在高频创建场景下明显。

但如果你需要自定义删除器(比如关闭文件描述

符、调用 sqlite3_finalize),make_shared 无法传入删除器参数——它只接受构造参数列表,删除器必须通过 shared_ptr 的构造函数指定:

auto p1 = std::make_shared(42); // ✅ 简洁高效
auto p2 = std::shared_ptr(fopen("log.txt", "w"), [](FILE* f) { fclose(f); }); // ✅ 自定义删除器
// auto p3 = std::make_shared("log.txt", "w", [](FILE* f){...}); // ❌ 编译失败
  • make_shared 不支持自定义分配器(除非 C++20 的 allocate_shared
  • 若对象构造可能抛异常,make_shared 保证“全有或全无”:要么控制块+对象都成功,要么都不分配
  • 注意:make_shared 会转发参数给对象的构造函数,不会调用 operator new 的重载版本(除非你特化了分配器)

循环引用导致内存泄漏:weak_ptr 是解药,不是装饰品

当两个 shared_ptr 相互持有(比如双向链表节点、观察者与被观察者),引用计数永远不会降到 0,对象无法释放——这就是循环引用。编译器不会报错,运行时也无提示,只会悄悄吃掉内存。

典型场景:class Node { std::shared_ptr next; std::shared_ptr prev; };a->next = b;b->prev = a;,则 a 和 b 的引用计数各为 2,即使外部所有 shared_ptr 都离开作用域,它们仍互相“挽留”。

  • 解决方式:将其中一个方向改为 std::weak_ptr(如 prev),访问前用 lock() 转成临时 shared_ptr
  • weak_ptr 不增加引用计数,也不阻止对象销毁;lock() 返回空 shared_ptr 表示目标已被释放
  • 不要用 weak_ptr::operator->() 直接访问——它不检查有效性;必须先 if (auto p = wp.lock()) { use(*p); }

reset()、assign()、swap() 的行为差异影响资源释放时机

shared_ptr 的几个成员函数看似都能“换掉”当前指针,但释放旧资源的时机和异常安全性不同。

例如:p.reset(new int(10)); 先销毁旧对象,再构造新对象;而 p = std::make_shared(10); 是先构造新对象、再交换控制块、最后销毁旧对象——后者更安全,因为如果构造失败(如内存不足),原 p 不受影响。

  • reset():立即释放当前所拥有的资源;若传入新指针,会先释放旧资源再接管新资源;不提供异常安全保证
  • operator=(赋值):强异常安全——新资源构造成功后才释放旧资源
  • swap():无异常,常用于避免临时对象开销,比如在函数返回前交换局部 shared_ptr 和成员变量
  • 慎用 get():返回裸指针,但不转移所有权;若用它构造另一个 shared_ptr(如 shared_ptr(p.get())),会引发双重释放

引用计数本身轻量,但误用 weak_ptr、忽略线程边界、或把 get() 当万能接口,才是实际项目中最容易漏掉的坑。


# node  # c++  # 作用域  # red  # if  # 成员变量  # 成员函数  # 构造函数  # auto  # int  # 循环  # 指针  # 接口  # class  # operator  # 线程  # 多线程  # 并发  # 对象  # 自定义  # 也不  # 它不  # 特化  # 几个  # 如果你  # 都不  # 多个  # 才是  # 这就是 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel怎么实现微信登录_Laravel Socialite第三方登录集成  Laravel如何与Docker(Sail)协同开发?(环境搭建教程)  JavaScript如何实现继承_有哪些常用方法  焦点电影公司作品,电影焦点结局是什么?  详解阿里云nginx服务器多站点的配置  非常酷的网站设计制作软件,酷培ai教育官方网站?  Laravel如何配置和使用队列处理异步任务_Laravel队列驱动与任务分发实例  Laravel定时任务怎么设置_Laravel Crontab调度器配置  Laravel如何实现API版本控制_Laravel API版本化路由设计策略  Python文件流缓冲机制_IO性能解析【教程】  北京网站制作费用多少,建立一个公司网站的费用.有哪些部分,分别要多少钱?  LinuxShell函数封装方法_脚本复用设计思路【教程】  如何在阿里云高效完成企业建站全流程?  轻松掌握MySQL函数中的last_insert_id()  如何在云主机快速搭建网站站点?  Python高阶函数应用_函数作为参数说明【指导】  Win11搜索栏无法输入_解决Win11开始菜单搜索没反应问题【技巧】  常州企业网站制作公司,全国继续教育网怎么登录?  如何在Windows环境下新建FTP站点并设置权限?  香港服务器部署网站为何提示未备案?  Laravel项目结构怎么组织_大型Laravel应用的最佳目录结构实践  如何快速查询域名建站关键信息?  高性能网站服务器部署指南:稳定运行与安全配置优化方案  Laravel集合Collection怎么用_Laravel集合常用函数详解  谷歌Google入口永久地址_Google搜索引擎官网首页永久入口  敲碗10年!Mac系列传将迎来「触控与联网」双革新  Laravel如何实现本地化和多语言支持_Laravel多语言配置与翻译文件管理  如何正确下载安装西数主机建站助手?  手机钓鱼网站怎么制作视频,怎样拦截钓鱼网站。怎么办?  Java垃圾回收器的方法和原理总结  宙斯浏览器怎么屏蔽图片浏览 节省手机流量使用设置方法  WordPress 子目录安装中正确处理脚本路径的完整指南  如何用景安虚拟主机手机版绑定域名建站?  html5的keygen标签为什么废弃_替代方案说明【解答】  如何打造高效商业网站?建站目的决定转化率  JS去除重复并统计数量的实现方法  如何在Tomcat中配置并部署网站项目?  网易LOFTER官网链接 老福特网页版登录地址  绝密ChatGPT指令:手把手教你生成HR无法拒绝的求职信  Laravel怎么实现模型属性的自动加密  Midjourney怎样加参数调细节_Midjourney参数调整技巧【指南】  如何用5美元大硬盘VPS安全高效搭建个人网站?  如何用好域名打造高点击率的自主建站?  iOS发送验证码倒计时应用  Laravel怎么生成URL_Laravel路由命名与URL生成函数详解  历史网站制作软件,华为如何找回被删除的网站?  今日头条AI怎样推荐抢票工具_今日头条AI抢票工具推荐算法与筛选【技巧】  Laravel怎么使用Session存储数据_Laravel会话管理与自定义驱动配置【详解】  佛山网站制作系统,佛山企业变更地址网上办理步骤?  Windows驱动无法加载错误解决方法_驱动签名验证失败处理步骤