彻底搞懂 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智能指针核心功底。

相关新闻

从零搭建AI搜索系统,这3类技术组合正在被淘汰:2024 Q2最新Gartner评估矩阵权威解读

从零搭建AI搜索系统,这3类技术组合正在被淘汰:2024 Q2最新Gartner评估矩阵权威解读

更多请点击: https://codechina.net 第一章:从零搭建AI搜索系统,这3类技术组合正在被淘汰:2024 Q2最新Gartner评估矩阵权威解读 Gartner 2024年第二季度《AI-Augmented Search Platform Magic Quadrant》明确指出:传统…

2026/7/22 16:06:49阅读更多 →
TI EMIFA SDRAM配置与初始化全解析:从原理到实战避坑指南

TI EMIFA SDRAM配置与初始化全解析:从原理到实战避坑指南

1. 项目概述与核心价值在嵌入式系统开发中,尤其是基于德州仪器(TI)C6000系列DSP或类似处理器的项目中,外部存储器接口(EMIFA)是连接处理器与外部SDRAM的关键桥梁。SDRAM以其高带宽和低成本的优势&#xff0…

2026/7/22 16:06:49阅读更多 →
【亲测免费】 探索与创新:EhSyringe - 让E站沟通无阻

【亲测免费】 探索与创新:EhSyringe - 让E站沟通无阻

探索与创新:EhSyringe - 让E站沟通无阻 在浩瀚的网络世界中,语言是连接全球用户的桥梁。对于深度网络爱好者来说,E站无疑是一个宝藏之地,但其英文为主的界面却可能成为一道难以逾越的屏障。为此,我们向您隆重推荐EhSy…

2026/7/22 16:06:49阅读更多 →
模型越做越大,产线却越跑越慢,怎么破?

模型越做越大,产线却越跑越慢,怎么破?

近年来,随着深度学习技术的飞速发展,模型参数量从百万级跃升至万亿级已不再是新闻。从BERT到GPT-3,再到如今的GPT-4、Claude-3,模型的“大”带来了前所未有的能力突破——更强的泛化性、更丰富的上下文理解、更精准的生成效果。然…

2026/7/22 17:04:59阅读更多 →
江棉斌:一个做新媒体培训机构的老板为什么要开发AI?

江棉斌:一个做新媒体培训机构的老板为什么要开发AI?

一个做新媒体培训机构的老板,自己烧钱开发新媒体营销AI,原因是当下新媒体行业已全面进入AI深度迭代与存量博弈的新周期。流量成本持续高企,内容同质化严重,平台算法频繁变动,大量企业虽持续投入人力与资金,…

2026/7/22 17:04:59阅读更多 →
天津河西区本地GEO获客怎么选?看这三点就够了

天津河西区本地GEO获客怎么选?看这三点就够了

很多河西区的实体老板都在问:现在同城生意越来越难做,有没有一种方式,能让客户在问AI助手的时候,直接推荐到我们店?其实这种需求背后对应的正是本地GEO获客。在河西区陈塘庄,有一家专注这个方向的服务商——…

2026/7/22 17:04:59阅读更多 →
0 基础入门React Native鸿蒙跨平台开发:useWindowDimensions会在屏幕尺寸变化时自动更新获取到的设备width和height值

0 基础入门React Native鸿蒙跨平台开发:useWindowDimensions会在屏幕尺寸变化时自动更新获取到的设备width和height值

本文是基于HarmonyOS API 24的进行的ReactNative 鸿蒙跨平台开发依托适配鸿蒙的 RN 运行层,使用 React 与 JS 编写一套业务代码,无需大量 ArkTS 原生开发,通用业务实现代码复用,支持按需扩展原生桥调用鸿蒙特有能力,有…

2026/7/22 17:04:59阅读更多 →
0 基础入门React Native鸿蒙跨平台开发:Vibration设备振动功能演示

0 基础入门React Native鸿蒙跨平台开发:Vibration设备振动功能演示

本文是基于HarmonyOS API 24的进行的ReactNative 鸿蒙跨平台开发依托适配鸿蒙的 RN 运行层,使用 React 与 JS 编写一套业务代码,无需大量 ArkTS 原生开发,通用业务实现代码复用,支持按需扩展原生桥调用鸿蒙特有能力,有…

2026/7/22 17:04:59阅读更多 →
计算机毕业设计之基于SpringBoot的全民健康守护平台设计与实现

计算机毕业设计之基于SpringBoot的全民健康守护平台设计与实现

摘要基于Spring Boot的全民健康守护平台,采用Java语言进行开发,利用Spring Boot框架的简洁性和高效性,构建出稳定且易于维护的后端服务。前端则运用Vue框架,提供流畅且用户友好的交互界面。数据存储方面,平台选用MySQL…

2026/7/22 17:02:59阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/22 0:53:59阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/22 0:53:59阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/22 0:53:59阅读更多 →
中小企业小程序开发公司怎么选:预算、上手和售后避坑指南

中小企业小程序开发公司怎么选:预算、上手和售后避坑指南

中小企业做小程序,最常见的矛盾是预算有限,但又不希望功能太单薄;没有技术团队,但又希望后续能自己运营;想快速上线,又担心隐性收费和售后失联。选型时如果只看“低价套餐”或“案例数量”,很容…

2026/7/22 0:01:17阅读更多 →
GEO优化如何沉淀长期内容资产?广拓时代谈AI搜索时代的内容ROI

GEO优化如何沉淀长期内容资产?广拓时代谈AI搜索时代的内容ROI

企业做营销,最怕钱花完了,资产没有留下。 效果广告能带来一段时间的曝光,但预算停止后,流量往往也随之停止。短视频内容可能在几天内冲高,也可能很快沉下去。AI搜索时代,企业需要重新思考一个问题&#xff…

2026/7/22 0:01:17阅读更多 →
Agent 终态判定:何时该停止思考、给出最终回复

Agent 终态判定:何时该停止思考、给出最终回复

Agent 终态判定:何时该停止思考、给出最终回复 一、你的 Agent 在"再想想"的循环里绕了 12 轮,用户已经关窗口了 Agent 与人最大的区别是:人知道什么时候该停下来给答案,Agent 会一直"想"下去。你给 Agent 接…

2026/7/22 0:01:17阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/21 22:53:50阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/21 18:53:30阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/21 18:53:30阅读更多 →