当前位置: 首页 > news >正文

优秀开源内容转自公众号后端开发成长指南

背景:在C++中,手动管理资源(new/delete)容易出错,导致:内存泄漏,重复释放,异常安全等问题。

为了解决这些问题,C++11引入了智能指针:

std::unique_ptr:独占所有权,std::shared_ptr 共享所有权,通过引用计数来管理生命周期

原理:

unique_ptr:目标是独占所有权,不可复制,只能移动(move)

使用场景:资源唯一所有权,RAII风格自动释放;

核心问题是:避免内存泄漏,支持移动语义。

核心原理:

1.独占所有权:唯一指针拥有资源,拷贝被禁用

2.移动语义:通过移动实现资源转移:

3.RAII:生命周期结束自动释放。

3.性能特点:

内存占用小(只存储原始指针和删除器)

无额外引用计数开销

编译器开销低

异常安全,必变资源泄漏;

shared_ptr:目标是多方共享资源,引用计数控制生命周期。

使用场景:多线程共享对象,缓存管理。

核心问题:

自动管理生命周期;处理多线程环境下的引用计数(atomic)

避免循环引用问题

1.内部结构:核心在于引用计数和控制块。

shared_ptr<T>
├── T* ptr           // 实际对象指针
└── ControlBlock* cb // 引用计数和删除器

控制块结构示例

2.核心逻辑:

1)构造shared_ptr:初始化use_count = 1;

2) 拷贝shared_ptr:use_count++(原子操作)

3)析构shared_ptr:use_count--,若为0调用删除器销毁对象;

weak_ptr:不增加use_count,仅增加weak_count,用于解决循环引用。

3.关键实现细节:

线程安全:std::atomic 保证引用计数在多线程下正确;

控制块分离:

对象和控制快分离,可以支持make_shared内联分配(减少内存碎片)

循环引用问题:

两个shared_ptr互相循环引用会导致引用计数不为0

需要weak_ptr解决;

4.性能分析:

unique_ptr vs shared_ptr

1.内存占用:

小,指针+删除器; 控制块额外开销,atomic计数

2. 拷贝/移动开销

禁止拷贝,移动开销小; 拷贝需要原子操作,移动开销小

3.多线程安全

依赖外部保护; 引用计数原子操作保证安全

关键区别:引用计数的原子性

cpp

// ❌ unique_ptr - 所有权转移不是线程安全的
class BadExample {std::unique_ptr<Data> data;
public:// 线程1调用void thread1() {auto temp = std::move(data); // 危险!没有同步// data现在是nullptr}// 线程2调用void thread2() {if (data) { // 竞态条件!data->process(); // 可能崩溃}}
};// ✅ shared_ptr - 引用计数操作是原子的
class GoodExample {std::shared_ptr<Data> data;
public:// 线程1调用void thread1() {auto local_copy = data; // 原子地增加引用计数if (local_copy) {local_copy->process(); // 安全使用}} // 原子地减少引用计数// 线程2调用  void thread2() {auto local_copy = data; // 原子地增加引用计数if (local_copy) {local_copy->process(); // 安全使用}} // 原子地减少引用计数
};

更好的对比示例

cpp

// unique_ptr - 必须用锁保护所有权转移
class TaskProcessor {std::unique_ptr<Task> current_task;std::mutex task_mutex;
public:void process() {std::unique_ptr<Task> local_task;{std::lock_guard<std::mutex> lock(task_mutex);local_task = std::move(current_task); // 必须在锁内移动}if (local_task) {local_task->execute();}}void setTask(std::unique_ptr<Task> task) {std::lock_guard<std::mutex> lock(task_mutex); // 必须加锁current_task = std::move(task);}
};// shared_ptr - 复制操作本身是线程安全的
class DataCache {std::shared_ptr<CachedData> cached_data;// 注意:这里不需要mutex保护shared_ptr的复制!
public:void updateCache(std::shared_ptr<CachedData> new_data) {cached_data = new_data; // 原子操作,无需加锁}std::shared_ptr<CachedData> getData() {return cached_data; // 原子操作,无需加锁}void process() {auto local_data = getData(); // 线程安全地获取副本if (local_data) {// 即使其他线程调用updateCache(),local_data仍然有效local_data->doWork(); }}
};

4.循环引用风险

无; 有,需要weak_ptr

5.适用场景:

独占资源 RAII; 多方共享对象管理,缓存,异步任务。

核心思想:能用 unique_ptr 就用 unique_ptr,能静态分析生命周期,避免 atomic 开销;需要多方共享才用 shared_ptr,并结合 weak_ptr 避免循环引用。

总结:

unique_ptr:

独占资源所有权,低开销,RAII自动释放

通过移动语义实现所有权转移

shared_ptr:共享资源,通过引用计数和控制块管理生命周期

支持多线程,但有性能开销

循环引用需要weak_ptr解决。

在分布式系统

hotspot 对象尽量用 unique_ptr

异步回调、共享上下文用 shared_ptr

一句话总结
“能唯一所有权就用 unique_ptr,必须共享就用 shared_ptr,控制引用计数就是控制你的性能。”

struct ControlBlock {std::atomic<size_t> use_count;   // 强引用计数std::atomic<size_t> weak_count;  // 弱引用计数Deleter deleter;                 // 删除器T* ptr;                          // 指向对象
};

文章转载自:

http://iJx0Z7gr.gkxyy.cn
http://a1MUfIlr.gkxyy.cn
http://1PBhZ587.gkxyy.cn
http://qHNCkiLa.gkxyy.cn
http://FtQw2wYf.gkxyy.cn
http://mZYVlMOS.gkxyy.cn
http://9SvHuyR4.gkxyy.cn
http://Q8i8D3M2.gkxyy.cn
http://prSt9sLy.gkxyy.cn
http://MKCCNkCF.gkxyy.cn
http://OCqDlozi.gkxyy.cn
http://aiFnDb3E.gkxyy.cn
http://Y9oBLdih.gkxyy.cn
http://CAXh4bjx.gkxyy.cn
http://CrLSpya6.gkxyy.cn
http://Y3EFALO6.gkxyy.cn
http://B0aCRfFO.gkxyy.cn
http://QKAGnWY1.gkxyy.cn
http://AphVAS53.gkxyy.cn
http://PKS5Ja9o.gkxyy.cn
http://jqWdcPr8.gkxyy.cn
http://cekvi5rj.gkxyy.cn
http://ixA7eS59.gkxyy.cn
http://V5jIpgKo.gkxyy.cn
http://0sPCxQHJ.gkxyy.cn
http://FQFyUH3B.gkxyy.cn
http://GVLa6vzF.gkxyy.cn
http://WTeFKZAt.gkxyy.cn
http://7DG2d9iT.gkxyy.cn
http://6nVL2iDr.gkxyy.cn
http://www.dtcms.com/a/363337.html

相关文章:

  • QuickUp-Ubuntu
  • js设计模式-职责链模式
  • 【音视频】Opus 编码格式介绍
  • WPF应用程序资源和样式的使用示例
  • HarmonyOS 应用开发新范式:深入剖析 Stage 模型与 ArkUI 最佳实践
  • 基于vue3和springboot框架集成websocket
  • 网络数据包是怎么在客户端和服务端之间进行传输的?
  • C#实现与西门子S7-1200_1500 PLC通信
  • qt QWebSocket详解
  • 系统扩展策略
  • 【LeetCode_26】删除有序数组中的重复项
  • 小迪web自用笔记24
  • GPT-5论文选题实测:如何从2000篇文献中提炼出3个可快速落地的高命中选题?
  • 从零开始学Vue3:Vue3的生命周期
  • Leetcode二分查找(4)
  • 开悟篇Docker从零到实战一篇文章搞定
  • 洗衣店小程序的设计与实现
  • GDB 调试
  • 深度学习篇---DenseNet网络结构
  • Spring Boot手写10万敏感词检查程序
  • C#----异步编程
  • 基于Django的论坛系统设计与实现(代码+数据库+LW)
  • Qt模型/视图编程详解:QStringListModel与多视图数据同步
  • 链表题类型注解解惑:理解Optional,理解ListNode
  • 前端实现解析【导入】数据后调用批量处理接口
  • GaussDB 等待事件为LockMgrLock处理方法
  • 为什么程序员总是发现不了自己的Bug?
  • flutter踩坑插件:Swift架构不兼容
  • 疯狂星期四文案网第58天运营日记
  • 手撕Redis底层2-网络模型深度剖析