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

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

https://www.php.cn/faq/2030763.html

发表回复

Your email address will not be published. Required fields are marked *