0

0

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

冰火之心

冰火之心

发布时间:2026-01-26 04:09:34

|

413人浏览过

|

来源于php中文网

原创

shared_ptr的引用计数是原子的,但仅保证同一shared_ptr对象的拷贝/赋值/析构线程安全;其指向资源的访问仍需手动同步,且循环引用需用weak_ptr破除。

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

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 的构造函数指定:

立即学习C++免费学习笔记(深入)”;

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,对象无法释放——这就是循环引用。编译器不会报错,运行时也无提示,只会悄悄吃掉内存。

Warp
Warp

新一代的终端工具(内置AI命令搜索)

下载

典型场景: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() 当万能接口,才是实际项目中最容易漏掉的坑。

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

771

2023.08.22

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

401

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

543

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

53

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

197

2025.08.29

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1072

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

148

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1016

2025.12.29

c++ 根号
c++ 根号

本专题整合了c++根号相关教程,阅读专题下面的文章了解更多详细内容。

58

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 6.8万人学习

前端基础到实战(HTML5+CSS3+ES6+NPM)
前端基础到实战(HTML5+CSS3+ES6+NPM)

共162课时 | 19万人学习

第二十二期_前端开发
第二十二期_前端开发

共119课时 | 12.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号