彻底搞懂 shared_ptr:底层结构、原子线程安全、强弱计数、手写简易实现
本篇文章一次性彻底梳理清楚从底层内存结构、双指针模型、控制块、原子计数、线程安全、weak_ptr 原理、内存释放时机最后手写一份极简可运行的 shared_ptr / weak_ptr吃透 C 智能指针核心。一、先纠正最大误区shared_ptr 内部不是一个指针是两个1. 完整成员结构很多初学者误区shared_ptr 只是一个包装过的裸指针。完全错误。标准库中一个 shared_ptr 对象内部固定包含两个指针我们可以通过简易类声明直观看到底层成员结构// shared_ptr 极简底层声明标准库核心结构templatetypenameTclassshared_ptr{private:T*_ptr;// 1. 数据指针指向业务堆对象ControlBlock*_ctrl;// 2. 控制块指针指向独立的计数控制块};两个指针各司其职数据指针 T* _ptr指向用户真正需要管理的堆内存对象控制块指针 ControlBlock* _ctrl指向堆上独立的控制块存放引用计数、析构信息、内存释放逻辑核心关键点多个 shared_ptr 指向同一个对象时所有指针共享同一个控制块计数统一管理。这也是为什么对象销毁 ≠ 控制块销毁。二、核心控制块 ControlBlock 结构一切原理的根源1. 控制块内部核心成员控制块是独立堆内存和业务对象内存分离核心结构极简声明如下// 控制块核心极简声明structControlBlock{std::atomiclonguse_cnt;// 强引用计数std::atomiclongweak_cnt;// 弱引用计数// 类型擦除对象析构、控制块释放函数void(*destroy)(void*);void(*dealloc)(ControlBlock*);};use_count强引用计数记录当前存活的 shared_ptr 数量管控业务对象生命周期weak_count弱引用计数记录当前存活的 weak_ptr 数量管控控制块内存生命周期为什么必须是atomic 原子变量多线程下会并发读写计数普通变量会触发数据竞争导致计数错乱、双重析构、内存泄漏、程序崩溃。原子变量保证计数操作线程安全。2. 两个计数的生命周期规则很重要这是面试最高频考点也是区分初级、中级开发者的关键use_count 变为 0立即销毁托管的业务对象但不释放控制块还有 weak_ptr 需要读取控制块状态use_count 0 且 weak_count 0才会真正释放控制块内存彻底回收所有内存一句话总结对象销毁看强计数控制块销毁看强弱双计数。三、引用计数变更场景 必考1. 强计数 use_count原子递增shared_ptr 拷贝构造shared_ptr 拷贝赋值weak_ptr.lock() 成功升级为 shared_ptr2. 强计数 use_count--原子递减shared_ptr 析构shared_ptr 主动 reset()shared_ptr 被赋值为空 / 被覆盖赋值3.完全不修改任何计数移动构造、移动赋值移动语义只是转移两个指针的所有权源指针置空无任何原子操作、无计数变更性能远高于拷贝。4. 弱计数 weak_count 变更weak_ptr 创建、拷贝weak_countweak_ptr 析构、赋值覆盖weak_count--weak_ptr 永远不影响 use_count不阻止对象销毁四、shared_ptr 线程安全很多人只会背“智能指针线程安全”但不知道哪里安全、哪里不安全。1. 安全的场景多线程并发拷贝同一个 shared_ptr原子计数递增安全多线程并发析构各自的 shared_ptr原子计数递减安全多线程并发 weak_ptr.lock() / expired()CAS 原子操作安全2. 绝对不安全的场景极易踩坑多线程同时修改同一个 shared_ptr 实例p.reset()、pxxx、pnullptr原因内部的 T*、ControlBlock* 两个裸指针不是原子的并发写会指针撕裂、野指针、崩溃多线程读写托管对象本身的数据shared_ptr 只管生命周期不管业务数据竞争需要手动加锁3. 补充内存序简单理解std::memory_order_relaxed只保证变量原子性不做内存屏障允许指令自由重排。std::memory_order_release屏障当前原子操作之前的所有读写不能重排到原子操作之后。std::memory_order_acquire屏障当前原子操作之后的所有读写不能重排到原子操作之前。std::memory_order_acq_rel同时拥有 acquire release 屏障原子操作前后指令都不允许跨重排。shared_ptr内存序使用规则 底层原因1. 计数递增统一使用 relaxed场景拷贝构造、拷贝赋值、weak_ptr::lock 成功递增 use_count、weak_ptr 构造拷贝递增 weak_count。原因计数递增是纯统计行为不涉及对象销毁、内存释放没有依赖关系。指令重排不会引发BUG使用 relaxed 可以省去内存屏障开销性能最优。2. 计数递减统一使用 acq_rel场景shared_ptr 析构/reset/赋值覆盖、weak_ptr 析构/赋值覆盖。原因递减计数是资源销毁的临界操作。需要 release 保证对象析构、内存读写等前置操作一定在计数递减前完成需要 acquire 保证读取到最新计数后再执行后续的内存释放判断。杜绝指令重排导致的「先减计数、后析构对象」的致命乱序问题。3. 单纯读取计数使用 acquire场景判断 weak_count 是否为0、lock 预读 use_count、expired() 判断状态。原因必须保证读到最新的、已经落地完成的计数状态再执行后续逻辑避免读到旧数据导致误判、空指针、双重释放。五、weak_ptr 核心原理 循环引用解决方案1. weak_ptr 内部结构weak_ptr 底层内存布局和 shared_ptr完全一致同样是双指针结构极简声明如下// weak_ptr 极简底层声明templatetypenameTclassweak_ptr{private:T*_ptr;// 数据指针指向托管对象ControlBlock*_ctrl;// 控制块指针指向共享的计数控制块};唯一区别weak_ptr 不会递增强引用计数 use_cnt仅观测对象状态不持有对象生命周期无法直接访问对象。2. lock() 核心逻辑lock() 的核心就是CAS 无锁原子争抢对应核心源码就是compare_exchange_weak用来线程安全的尝试提升强引用计数// CAS 核心代码lock 关键逻辑longcntctrl_-use_cnt.load(std::memory_order_acquire);// 循环 CAS 重试防止多线程竞争失败while(cnt0){// 预期值cnt 当前内存值就更新为 cnt1// 竞争失败则刷新cnt继续重试if(ctrl_-use_cnt.compare_exchange_weak(cnt,cnt1,std::memory_order_acq_rel,std::memory_order_acquire)){// CAS 成功强计数1返回可用 shared_ptrreturnSharedPtrT(ptr_,ctrl_);}}// use_cnt 0对象已销毁returnSharedPtrT();CAS 核心逻辑通过compare_exchange_weak无锁原子操作并发安全地尝试将强计数 1避免加锁开销。while 循环作用处理多线程竞争失败和compare_exchange_weak伪失败场景CAS 失败时会通过acquire 原子内存序读取内存中 use_cnt 的最新值并覆盖更新 cnt不断自旋重试确保并发场景下逻辑绝对正确。3. 解决循环引用两个对象互相持有 shared_ptr会导致双方 use_count 永远无法归 0造成内存泄漏。解决方案将其中一方改为 weak_ptr不增加强计数对象可正常析构。六、手写极简版 shared_ptr weak_ptr可直接运行基于上面所有底层原理手写一套轻量简易版本贴合标准库核心设计逻辑代码精简易懂方便直观理解智能指针的完整工作流程。#includeatomic#includeutility// 前置声明templatetypenameTclassWeakPtr;// 控制块结构体structControlBlock{std::atomiclonguse_cnt{0};// 强计数std::atomiclongweak_cnt{0};// 弱计数// 类型擦除回调对象析构、控制块释放void(*destroy)(void*)nullptr;void(*dealloc)(ControlBlock*)nullptr;};// 简易 SharedPtr 实现templatetypenameTclassSharedPtr{private:T*ptr_nullptr;ControlBlock*ctrl_nullptr;// 私有构造仅供 WeakPtr::lock 升级使用SharedPtr(T*p,ControlBlock*c):ptr_(p),ctrl_(c){if(ctrl_){ctrl_-use_cnt.fetch_add(1,std::memory_order_relaxed);}}// 授权 WeakPtr 访问私有成员friendclassWeakPtrT;// 释放当前资源voidrelease(){if(!ctrl_){return;}// 强计数递减获取递减后的最新值ctrl_-use_cnt.fetch_sub(1,std::memory_order_acq_rel);// 为了和原理贴合直接在读取一边递减的值直观longnew_usectrl_-use_cnt.load(std::memory_order_acquire);if(new_use0){// 强计数归0销毁托管对象if(ctrl_-destroy){ctrl_-destroy(ptr_);}ptr_nullptr;// 弱计数为0彻底释放控制块longold_weakctrl_-weak_cnt.load(std::memory_order_acquire);if(old_weak0){if(ctrl_-dealloc){ctrl_-dealloc(ctrl_);}ctrl_nullptr;}}}public:// 普通构造explicitSharedPtr(T*pnullptr):ptr_(p){if(ptr_){ctrl_newControlBlock;ctrl_-use_cnt1;ctrl_-weak_cnt0;ctrl_-destroy[](void*obj){deletestatic_castT*(obj);};ctrl_-dealloc[](ControlBlock*cb){deletecb;};}}// 拷贝构造SharedPtr(constSharedPtrother):ptr_(other.ptr_),ctrl_(other.ctrl_){if(ctrl_){ctrl_-use_cnt.fetch_add(1,std::memory_order_relaxed);}}// 拷贝赋值SharedPtroperator(constSharedPtrother){if(this!other){release();ptr_other.ptr_;ctrl_other.ctrl_;if(ctrl_){ctrl_-use_cnt.fetch_add(1,std::memory_order_relaxed);}}return*this;}// 移动构造SharedPtr(SharedPtrother)noexcept:ptr_(other.ptr_),ctrl_(other.ctrl_){other.ptr_nullptr;other.ctrl_nullptr;// 移动语义不修改引用计数}// 移动赋值SharedPtroperator(SharedPtrother)noexcept{if(this!other){release();ptr_other.ptr_;ctrl_other.ctrl_;other.ptr_nullptr;other.ctrl_nullptr;// 移动语义不修改引用计数}return*this;}// 析构函数~SharedPtr(){release();}// 对外工具接口T*get()const{returnptr_;}Toperator*()const{return*ptr_;}T*operator-()const{returnptr_;}longuse_count()const{returnctrl_?ctrl_-use_cnt.load():0;}boolunique()const{returnuse_count()1;}voidreset(T*pnullptr){release();ptr_p;ctrl_nullptr;if(p){ctrl_newControlBlock;ctrl_-use_cnt1;ctrl_-weak_cnt0;ctrl_-destroy[](void*obj){deletestatic_castT*(obj);};ctrl_-dealloc[](ControlBlock*cb){deletecb;};}}};// 简易 WeakPtr 实现templatetypenameTclassWeakPtr{private:T*ptr_nullptr;ControlBlock*ctrl_nullptr;public:WeakPtr()default;// 从 SharedPtr 构造弱指针WeakPtr(constSharedPtrTsp):ptr_(sp.ptr_),ctrl_(sp.ctrl_){if(ctrl_){ctrl_-weak_cnt.fetch_add(1,std::memory_order_relaxed);}}// 拷贝构造WeakPtr(constWeakPtrother):ptr_(other.ptr_),ctrl_(other.ctrl_){if(ctrl_){ctrl_-weak_cnt.fetch_add(1,std::memory_order_relaxed);}}// 拷贝赋值WeakPtroperator(constWeakPtrother){if(this!other){if(ctrl_){ctrl_-weak_cnt.fetch_sub(1,std::memory_order_acq_rel);}ptr_other.ptr_;ctrl_other.ctrl_;if(ctrl_){ctrl_-weak_cnt.fetch_add(1,std::memory_order_relaxed);}}return*this;}// 析构~WeakPtr(){if(ctrl_){// fetch_sub 返回的是老值。所以判断需要1longoldctrl_-weak_cnt.fetch_sub(1,std::memory_order_acq_rel);// 强弱计数都为0释放控制块if(old1ctrl_-use_cnt.load()0){if(ctrl_-dealloc){ctrl_-dealloc(ctrl_);}}}}// 弱指针升级为强指针CAS 无锁线程安全SharedPtrTlock()const{if(!ctrl_){returnSharedPtrT();}longcntctrl_-use_cnt.load(std::memory_order_acquire);while(cnt0){if(ctrl_-use_cnt.compare_exchange_weak(cnt,cnt1,std::memory_order_acq_rel,std::memory_order_acquire)){returnSharedPtrT(ptr_,ctrl_);}}returnSharedPtrT();}// 判断对象是否已销毁boolexpired()const{returnctrl_?(ctrl_-use_cnt.load()0):true;}};以上手写实现为精简教学版本完整对齐 C11 标准库核心设计思想剔除了兼容冗余逻辑更适合原理学习。如需对照官方完整源码深入研读可查看微软开源 STL 标准库实现std::shared_ptr / std::weak_ptr 标准库源码七、全文核心知识点总结面试速背shared_ptr 内部两个指针数据指针、控制块指针不是一个控制块两个原子变量use_count对象生命周期、weak_count控制块生命周期计数变更拷贝加、析构/reset减、移动不动、lock加释放时机强计数0销毁对象强弱双0释放控制块线程安全边界计数操作安全同实例并发写不安全不保护业务数据weak_ptr不影响强计数、解决循环引用、lock原子升级结尾多数人学习智能指针仅停留在API调用和表层结论无法吃透底层逻辑、线程安全原理与内存释放细节面试中极易卡壳。本文从底层结构出发搭配可运行的手写精简实现完整打通 shared_ptr、weak_ptr 的强弱计数机制、原子线程安全、生命周期管理全链路逻辑。既能满足日常开发认知也可从容应对面试深挖提问彻底夯实C智能指针核心功底。

相关新闻

TI Sitara EMAC硬件QoS与帧分类:嵌入式网络确定性保障实践

TI Sitara EMAC硬件QoS与帧分类:嵌入式网络确定性保障实践

1. 项目概述与核心价值在嵌入式网络开发,尤其是工业控制、车载电子或实时音视频传输这类对网络延迟和确定性要求极高的领域,单纯依靠软件协议栈来处理数据包的优先级是远远不够的。软件处理会引入不可预测的调度延迟,当网络流量突发时&#x…

2026/8/21 7:05:29 阅读更多 →
AI 自动操控电脑 OpenClaw 2.7.9 部署排坑与长期使用技巧汇总(含安装包)

AI 自动操控电脑 OpenClaw 2.7.9 部署排坑与长期使用技巧汇总(含安装包)

🦞OpenClaw(小龙虾)部署实战教程|搭建属于自己的本地桌面数字员工 适配平台:Windows10/11(64 位)、macOS12 及以上 稳定版本:v2.7.9 特点✨:图形化引导部署、无需命令行…

2026/8/10 11:13:16 阅读更多 →
新手搭建桌面智能自动化 OpenClaw v2.7.9 解压部署完整流程(含安装包)

新手搭建桌面智能自动化 OpenClaw v2.7.9 解压部署完整流程(含安装包)

🦞 OpenClaw 本地 AI 自动化平台|图形化一键部署,省去繁琐环境配置 支持系统:Windows10/11 64 位、macOS12 及以上 当前稳定版本:v2.7.9 核心亮点✨:全程可视化操作,不用掌握命令行&#xff0c…

2026/8/20 1:01:33 阅读更多 →

最新新闻

Kimi    LeetCode LCP 09. 最小跳跃次数 Rust实现

Kimi LeetCode LCP 09. 最小跳跃次数 Rust实现

LeetCode LCP 09. 最小跳跃次数 - Rust 实现根据搜索结果,LCP 09 的题目描述如下:> 游戏机由 N 个特殊弹簧排成一排,编号为 0 到 N-1。初始有一个小球在编号 0 的弹簧处。若小球在编号为 i 的弹簧处,通过按动弹簧,可…

2026/8/21 7:05:39 阅读更多 →
Java程序员简历撰写指南:从技术深度到项目价值

Java程序员简历撰写指南:从技术深度到项目价值

1. Java程序员简历核心要素解析 对于月薪3万级别的Java开发岗位,简历需要突出技术深度和项目价值。我见过太多优秀候选人因为简历问题错失面试机会,这里分享一套经过验证的撰写方法论。 1.1 技术栈呈现技巧 资深Java工程师的技术栈展示要遵循"金…

2026/8/21 7:05:39 阅读更多 →
数学建模图论习题精解:从算法原理到建模实战

数学建模图论习题精解:从算法原理到建模实战

1. 项目概述:一份习题答案的价值与边界 最近在整理资料时,翻到了司守奎老师《数学建模算法与应用》第二版第四章的图论部分习题。这本书是很多数学建模爱好者和参赛者的“案头书”,其图论章节更是将抽象的图论知识与实际建模问题紧密结合的典…

2026/8/21 7:05:39 阅读更多 →
Golang面试全攻略:35道核心题目深度解析

Golang面试全攻略:35道核心题目深度解析

1. Golang面试题解析:从基础到高级的全面指南作为一名Golang开发者,面试是职业生涯中不可避免的重要环节。这份35道Golang面试题涵盖了从基础语法到高级特性的各个方面,帮助你在面试中游刃有余。我将结合实际开发经验,详细解析每道…

2026/8/21 7:05:39 阅读更多 →
C++仿函数:从函数对象到STL算法与Lambda表达式的核心机制

C++仿函数:从函数对象到STL算法与Lambda表达式的核心机制

1. 项目概述:为什么我们需要“仿函数”?在C的世界里,我们经常听到“函数对象”或者“仿函数”这个词。很多刚接触STL或者泛型编程的朋友可能会疑惑:明明有函数指针,为什么还需要仿函数?它看起来就像一个重载…

2026/8/21 7:05:38 阅读更多 →
主成分分析(PCA)实战指南:从数学原理到Python实现与建模应用

主成分分析(PCA)实战指南:从数学原理到Python实现与建模应用

1. 从“维数灾难”到“降维打击”:主成分分析的核心价值在数学建模,尤其是处理高维数据的竞赛或科研项目中,我们常常会陷入一种困境:手头的数据集变量众多,看似信息丰富,但直接分析时却感到无从下手&#x…

2026/8/21 7:04:38 阅读更多 →

日新闻

机场边检旅客定位系统国产化白皮书:算法、硬件、底座平台全程自主

机场边检旅客定位系统国产化白皮书:算法、硬件、底座平台全程自主

前言随着国家数字基础设施信创替代、关键技术自主可控战略持续深化,口岸智慧安防、边检智能管控领域正全面进入国产化、自主化、安全可控升级周期。当前国内机场边检旅客识别与定位体系长期依赖国外商用视觉算法、进口成像硬件、闭源通用计算平台,存在核…

2026/8/21 0:00:42 阅读更多 →
别再把“数字孪生”当空间智能了!镜像视界揭开四维时空的真正面纱

别再把“数字孪生”当空间智能了!镜像视界揭开四维时空的真正面纱

别再把“数字孪生”当空间智能了!镜像视界揭开四维时空的真正面纱当下数字化建设浪潮中,很多项目将三维可视化、视频贴图叠加的数字孪生等同于空间智能。传统数字孪生更多停留在三维场景复刻,擅长把物理世界“画出来、展示出来”,…

2026/8/21 0:00:42 阅读更多 →
105、车载温度范围-40°C到85°C的影像质量一致性——ISP参数温漂补偿与产线标定策略

105、车载温度范围-40°C到85°C的影像质量一致性——ISP参数温漂补偿与产线标定策略

105、车载温度范围-40C到85C的影像质量一致性——ISP参数温漂补偿与产线标定策略 去年冬天在北方某车厂做A样评审,凌晨四点的黑河试验场,零下三十三度。客户拿了一台冷启动的车,中控屏上倒车影像全是雪花噪点,暗部细节直接糊成一片。我第一反应是sensor温度没上来,暗电流…

2026/8/21 0:00:42 阅读更多 →

周新闻

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

如果你是一名开发者,最近可能已经感受到了AI大模型正在从“玩具”变成“生产力工具”的强烈信号。从代码补全到智能Agent,从本地部署到云端API,我们正处在一个技术栈快速重构的节点。然而,面对层出不穷的模型、框架和工具&#xf…

2026/8/21 3:21:33 阅读更多 →
工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/21 0:02:09 阅读更多 →
【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…

2026/8/21 6:07:56 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/20 6:11:08 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/20 21:46:49 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/21 0:14:22 阅读更多 →