尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

C++ 中 shared ptr 详解:原理与线程安全性分析

C++ 中 shared ptr 详解:原理与线程安全性分析
📅 发布时间:2026/7/26 10:50:47

C++ 中 shared_ptr 详解:原理与线程安全性分析


一、引言:共享所有权的智能指针


std::shared_ptr是 C++11 引入的智能指针,它实现了共享所有权(Shared Ownership)语义——多个shared_ptr可以同时拥有同一个对象,通过引用计数(Reference Counting)来追踪所有者的数量。当最后一个shared_ptr被销毁时,管理的对象会被自动释放。


与unique_ptr的独占所有权不同,shared_ptr适用于需要多个所有者共享同一对象的场景。但共享所有权带来了额外的复杂性:引用计数的存储、原子操作的开销、以及常见的循环引用问题(需要weak_ptr打破)。


二、核心概念速览


| 维度 | 说明 |

|------|------|

| 所有权模型 | 共享(多个 shared_ptr 可拥有同一对象) |

| 引用计数 | 追踪当前有多少个 shared_ptr 共享同一对象 |

| 拷贝语义 | 深拷贝(增加引用计数) |

| 移动语义 | 支持(转移所有权,不改变引用计数) |

| 对象释放时机 | 最后一个 shared_ptr 销毁时(引用计数归零) |

| 线程安全性 | 引用计数操作是线程安全的,但被管理对象本身不是 |

| 循环引用 | 导致内存泄漏,需用 weak_ptr 打破 |

| 内存开销 | 通常为裸指针的 2 倍(对象指针 + 控制块指针) |

| 性能开销 | 拷贝/析构涉及原子引用计数操作 |


三、shared_ptr 的内部结构


3.1 控制块和对象


shared_ptr内部包含两个指针:


  • 对象指针:指向被管理的对象
  • 控制块指针:指向控制块(Control Block),包含引用计数、弱引用计数、删除器等信息


控制块(0x2000)被管理对象(0x1000)shared_ptr2shared_ptr1

对象指针: 0x1000

控制块指针: 0x2000

对象指针: 0x1000

控制块指针: 0x2000

T 对象

shared_count: 2
(强引用计数)

weak_count: 1
(弱引用计数)

删除器

分配器


3.2 简化实现


template<typename T> class shared_ptr { private: T* ptr_; // 指向被管理的对象 control_block* cb_; // 指向控制块 struct control_block { size_t shared_count; // 强引用计数 size_t weak_count; // 弱引用计数 // 删除器和分配器... }; public: // 构造函数 explicit shared_ptr(T* p) : ptr_(p), cb_(new control_block{1, 0}) { } // 拷贝构造函数 shared_ptr(const shared_ptr& other) : ptr_(other.ptr_), cb_(other.cb_) { ++cb_->shared_count; // 增加引用计数 } // 析构函数 ~shared_ptr() { --cb_->shared_count; if (cb_->shared_count == 0) { delete ptr_; // 删除被管理对象 if (cb_->weak_count == 0) { delete cb_; // 删除控制块 } } } // 拷贝赋值运算符 shared_ptr& operator=(const shared_ptr& other) { if (this != &other) { // 先减少当前对象的引用计数 if (--cb_->shared_count == 0) { delete ptr_; if (cb_->weak_count == 0) delete cb_; } // 再增加新对象的引用计数 ptr_ = other.ptr_; cb_ = other.cb_; ++cb_->shared_count; } return *this; } T* get() const { return ptr_; } T* operator->() const { return ptr_; } T& operator*() const { return *ptr_; } size_t use_count() const { return cb_->shared_count; } };


四、引用计数的生命周期


对象控制块(引用计数)shared_ptr bshared_ptr a代码对象控制块(引用计数)shared_ptr bshared_ptr a代码a 和 b 共享同一对象a 析构,但不释放对象auto a = make_shared<T>()shared_count = 1创建对象auto b = a (拷贝)shared_count = 2a 离开作用域shared_count = 1b 离开作用域shared_count = 0delete 对象控制块也被释放(若无 weak_ptr)


五、线程安全性分析


5.1 引用计数的线程安全


shared_ptr的引用计数操作是线程安全的。C++ 标准要求shared_ptr的引用计数使用原子操作,因此多个线程同时拷贝/销毁指向同一对象的shared_ptr不会导致引用计数损坏。


#include <memory> #include <thread> #include <vector> #include <iostream> int main() { auto sp = std::make_shared<int>(42); std::vector<std::thread> threads; for (int i = 0; i < 100; ++i) { threads.emplace_back([sp]() { // 按值捕获,增加引用计数 // sp 的拷贝/析构是线程安全的 auto local = sp; // 引用计数原子递增 // local 析构时引用计数原子递减 }); } for (auto& t : threads) { t.join(); } std::cout << "Final use_count: " << sp.use_count() << std::endl; // 1 // 引用计数正确,没有被破坏 }


5.2 被管理对象的线程安全


重要区分:引用计数的线程安全不等于被管理对象的线程安全。


#include <memory> #include <thread> #include <iostream> class Counter { public: int value_ = 0; void increment() { ++value_; } // 不是线程安全的! }; int main() { auto counter = std::make_shared<Counter>(); std::vector<std::thread> threads; for (int i = 0; i < 1000; ++i) { threads.emplace_back([counter]() { for (int j = 0; j < 1000; ++j) { counter->increment(); // ❌ 数据竞争! // shared_ptr 的引用计数是安全的 // 但 Counter::value_ 的修改不是线程安全的 } }); } for (auto& t : threads) t.join(); std::cout << "Final value: " << counter->value_ << std::endl; // 期望 1,000,000,实际结果不确定(数据竞争) }


正确做法:对共享对象本身加锁保护,或使用原子操作。


#include <atomic> class ThreadSafeCounter { std::atomic<int> value_{0}; public: void increment() { ++value_; } // 原子操作,线程安全 int get() const { return value_.load(); } }; // 或使用互斥锁 class MutexCounter { int value_ = 0; mutable std::mutex mtx_; public: void increment() { std::lock_guard<std::mutex> lock(mtx_); ++value_; } };


5.3 线程安全性总结


| 操作 | 线程安全性 | 说明 |

|------|:---:|------|

| 拷贝 shared_ptr | 安全 | 引用计数原子递增 |

| 析构 shared_ptr | 安全 | 引用计数原子递减 |

| 移动 shared_ptr | 安全 | 不涉及引用计数修改 |

| 修改被管理对象 |不安全| 需要额外的同步机制 |

| use_count() | 安全(但值可能过期) | 返回值是近似值 |

| reset() | 安全 | 原子地替换管理的对象 |


六、make_shared 与直接构造的差异


6.1 内存布局对比


// 方式一:直接构造(两次内存分配) std::shared_ptr<int> sp1(new int(42)); // 1. new int(42) — 分配 int 对象 // 2. new control_block — 分配控制块 // 总共 2 次内存分配 // 方式二:make_shared(一次内存分配) auto sp2 = std::make_shared<int>(42); // 1. 分配一块连续内存,包含 int 对象和控制块 // 总共 1 次内存分配


make_shared(单块内存)

连续内存块
[控制块 | int 对象]

shared_ptr

int 对象部分

控制块部分

直接构造(newint+newcontrol_block)

控制块
shared_count: 1

shared_ptr

int 对象


6.2 make_shared 的优势与劣势


| 维度 | make_shared | 直接构造 |

|------|:---:|:---:|

| 内存分配次数 | 1 次 | 2 次 |

| 缓存局部性 | 好(对象和控制块相邻) | 差 |

| 异常安全 | 是 | 可能有风险 |

| 自定义删除器 | 不支持 | 支持 |

| 内存释放时机 | 可能延迟(弱引用存在时) | 对象及时释放 |


make_shared 的一个陷阱:如果有weak_ptr存在,即使所有shared_ptr都销毁了,整个内存块(包括对象)也不会释放,直到所有weak_ptr也销毁。


auto sp = std::make_shared<int>(42); std::weak_ptr<int> wp = sp; sp.reset(); // shared_count 归零,对象析构 // 但内存块没有被释放!因为 weak_count > 0 // 需要等到 wp 也销毁才能释放内存


七、enable_shared_from_this


#include <memory> #include <iostream> // 错误做法:从 this 创建多个 shared_ptr class BadExample { public: std::shared_ptr<BadExample> getShared() { return std::shared_ptr<BadExample>(this); // ❌ 危险! // 每次调用都创建新的控制块 // 导致同一对象有多个独立的引用计数 } }; // 正确做法:继承 enable_shared_from_this class GoodExample : public std::enable_shared_from_this<GoodExample> { public: std::shared_ptr<GoodExample> getShared() { return shared_from_this(); // 安全:共享同一个控制块 } static std::shared_ptr<GoodExample> create() { return std::shared_ptr<GoodExample>(new GoodExample()); } private: GoodExample() = default; // 私有构造函数,强制使用 create() }; int main() { auto obj = GoodExample::create(); auto obj2 = obj->getShared(); // 正确:共享同一个控制块 std::cout << "use_count: " << obj.use_count() << std::endl; // 2 }


八、循环引用与 weak_ptr


#include <memory> #include <iostream> struct B; struct A { std::shared_ptr<B> bPtr; ~A() { std::cout << "A destroyed" << std::endl; } }; struct B { std::shared_ptr<A> aPtr; // 导致循环引用 // 应该使用:std::weak_ptr<A> aPtr; ~B() { std::cout << "B destroyed" << std::endl; } }; int main() { auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->bPtr = b; b->aPtr = a; // 循环引用! // a 和 b 离开作用域 // 引用计数都不为 0 → 内存泄漏 // 不会输出 A destroyed 或 B destroyed }


循环引用A::bPtrB::aPtr离开作用域引用计数-1剩余:1离开作用域引用计数-1剩余:1

shared_ptr<A>
引用计数: 2
(main 中的 a + B::aPtr)

shared_ptr<B>
引用计数: 2
(main 中的 b + A::bPtr)

main 中的 a

main 中的 b

两者引用计数都不为 0
永远无法销毁


解决方案:使用weak_ptr打破循环:


struct B { std::weak_ptr<A> aPtr; // weak_ptr 不增加引用计数 ~B() { std::cout << "B destroyed" << std::endl; } };


九、总结


std::shared_ptr是 C++ 中实现共享所有权的智能指针,其核心原理和注意事项如下:


  1. 内部结构:shared_ptr包含对象指针和控制块指针。控制块存储引用计数(shared_count)、弱引用计数(weak_count)、删除器和分配器。多个shared_ptr共享同一控制块。


  1. 引用计数机制:拷贝构造/赋值时原子递增引用计数,析构时原子递减引用计数。当引用计数归零时,删除被管理对象;当弱引用计数也归零时,删除控制块。


  1. 线程安全性:
  • 引用计数操作是线程安全的(使用原子操作)
  • 被管理对象的访问不是线程安全的,需要额外的同步机制
  • 多个线程同时拷贝/销毁指向同一对象的shared_ptr是安全的
  • 多个线程同时修改被管理对象的状态是不安全的


  1. 最佳实践:
  • 优先使用std::make_shared<T>()创建shared_ptr
  • 使用std::weak_ptr打破循环引用
  • 需要从this获取shared_ptr时继承std::enable_shared_from_this
  • 不要混淆引用计数的线程安全与被管理对象的线程安全
  • 如果不需要共享所有权,优先使用std::unique_ptr(更轻量)


shared_ptr是 C++ 中功能最强大的智能指针,但也带来了最大的开销。理解其内部机制和线程安全性边界,才能在正确的场景中安全、高效地使用它。

相关新闻

  • 【AI自动化数据入库终极指南】:20年DBA亲授5大避坑法则与实时入库提速300%的实战秘钥
  • 机器学习在校园心理健康预警系统中的应用实践
  • 如何高效实现音频格式转换:专业NCM解密方案实战指南

最新新闻

  • TI CC13x2/CC26x2 AUX_SMPH与AUX_TDC寄存器实战解析与驱动开发
  • TMS320C54x串口仿真全解析:从标准模式到TDM的配置与避坑指南
  • Call-me性能优化:提升WebRTC通话质量的10个实用技巧
  • HarmonyOS @Local 完全指南:V2 组件私有状态的不可变更新模式
  • C++继承中的重名与构造析构:深入理解面向对象编程的核心机制
  • 【私密内参】头部AI实验室绝少公开的逻辑题压力测试框架:融合形式验证+反事实扰动+认知负荷建模

日新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号