C++智能指针:原理和使用,多种指针的区别,以及内存泄漏
目录一.智能指针的使用及原理1.1智能指针的使用场景分析1.2RAII和智能指针的设计思路1.3C标准库智能指针的使用1.4删除器1.5完善shared_ptr模拟实现1.6shared_ptr与weak_ptr1.6.1shared_ptr的循环引用问题1.6.2weak_ptr总结四种智能指针的区别1.7C11和boost中智能指针的关系1.8内存泄漏1.8.1 什么是内存泄漏内存泄漏的危害1.8.2 如何检测内存泄漏了解1.8.3 如何避免内存泄漏一.智能指针的使用及原理1.1智能指针的使用场景分析对比项mallocnew所属C 语言库函数头文件cstdlibC 运算符语言内置不需要头文件内存来源堆只分配原始内存不会调用构造函数堆分配内存 调用构造函数完成对象初始化返回值void*需要强制类型转换返回对应类型指针不用强转失败行为分配失败返回NULL 空指针分配失败默认抛出std::bad_alloc异常释放方式free()delete/delete[]同时调用析构函数数组支持手动算总字节大小malloc(sizeof(T)*N)new T[N]配套delete[]重载不能重载可以全局 / 类内重载operator new自定义处理无支持nothrow版本new(nothrow) T失败返回 nullptr 不抛异常Qoperator new和new的区别new运算符完整动作分配内存 调用构造函数operator new只是函数等价 malloc只申请内存不会调用构造函数。1.2RAII和智能指针的设计思路RAII 是 Resource Acquisition Is Initialization资源获取即初始化的缩写他是一种管理资源的类的设计思想本质是一种利用对象生命周期来管理获取到的动态资源避免资源泄漏这里的资源可以是内存、文件指针、网络连接、互斥锁等等。RAII 在获取资源时把资源委托给一个对象接着控制对资源的访问资源在对象的生命周期内始终保持有效最后在对象析构的时候释放资源这样保障了资源的正常释放避免资源泄漏问题。智能指针类除了满足 RAII 的设计思路还要方便资源的访问所以智能指针类还会想迭代器类一样重载operator*/operator-/operator[]等运算符方便访问资源。两者关系总结RAII 是通用原则不限于内存RAII 可以管理文件std::fstream、互斥锁std::lock_guard、socket不仅仅管堆内存。智能指针 RAII 应用于堆内存管理智能指针是RAII 的一个子集、具体实现专门用来管理new出来的堆内存资源。分工RAII思想规定「构造拿资源析构放资源」智能指针封装好的类帮你做内存的获取 / 自动释放不用手写 delete。智能指针本质就是一个栈上对象把堆资源托管给栈对象栈对象生命周期结束析构自动释放堆内存这就是 RAII。下面是大致的智能指针模拟后面我们会具体讲解各种指针的优缺点1.3C标准库智能指针的使用C 标准库中的智能指针都在memory这个头文件下面我们包含memory就可以是使用了智能指针有好几种除了weak_ptr他们都符合 RAII 和像指针一样访问的行为原理上而言主要是解决智能指针拷贝时的思路不同。auto_ptr是 C98 时设计出来的智能指针他的特点是拷贝时把被拷贝对象的资源的管理权转移给拷贝对象这是一个非常糟糕的设计因为他会到被拷贝对象悬空访问报错的问题C11 设计出新的智能指针后强烈建议不要使用auto_ptr。其他 C11 出来之前很多公司也是明令禁止使用这个智能指针的。unique_ptr是 C11 设计出来的智能指针他的名字翻译出来是唯一指针他的特点的不支持拷贝只支持移动。如果不需要拷贝的场景就非常建议使用他。shared_ptr是 C11 设计出来的智能指针他的名字翻译出来是共享指针他的特点是支持拷贝也支持移动。如果需要拷贝的场景就需要使用他了。底层是用引用计数的方式实现的。weak_ptr是 C11 设计出来的智能指针他的名字翻译出来是弱指针他完全不同于上面的智能指针他不支持 RAII也就意味着不能用它直接管理资源weak_ptr的产生本质是要解决shared_ptr的一个循环引用导致内存泄漏的问题。具体细节下面我们再细讲。auto_ptr指针我们发现程序错误退出为什么呢我们调试看一下拷贝赋值后原来的 ap1 资源被夺走内部指针置空继续使用 ap1 就崩溃这就是auto_ptr被废弃的根源。unique_ptr:移动后也会制空移动的本质就是调用移动构造实现资源交换所以如果不使用拷贝就用unique_ptr最方便shared_ptr:我们可以看到指向的是同一块地址然后共同管理这同一块资源模拟实现auto_ptr:namespace Jam { templateclass T class auto_ptr { public: auto_ptr(T* ptr) :_ptr(ptr) {} auto_ptr(auto_ptrT sp) :_ptr(sp._ptr) { sp._ptr nullptr; } // 赋值运算符重载 auto_ptrT operator(auto_ptrT up) { if (this ! up) { // 释放当前的对象接受被赋值的对象 if (_ptr) delete _ptr; _ptr up._ptr; up._ptr nullptr; } return *this; } ~auto_ptr() { if (_ptr) { delete _ptr; cout delete auto_ptr endl; } } T* operator-() { return _ptr; } T operator*() { return *_ptr; } private: T* _ptr; }; } int main() { Jam::auto_ptrDate u1(new Date(2026, 8, 20)); Jam::auto_ptrDate u2(u1); return 0; }模拟实现unique_ptr:namespace Jam { templateclass T class unique_ptr { public: unique_ptr(T* ptr) :_ptr(ptr) { } // unique_ptr不允许拷贝构造和运算符重载 unique_ptr(unique_ptrT up) delete; unique_ptrT operator(const unique_ptrT up) delete; // 可以移动拷贝构造 unique_ptr(unique_ptrT up) { _ptr up._ptr; up._ptr nullptr; } unique_ptrT operator(unique_ptrT up) { if (this ! up) { if (_ptr) delete _ptr; _ptr up._ptr; up._ptr nullptr; } return *this; } ~unique_ptr() { if (_ptr) delete _ptr; } T* operator-() { return _ptr; } T operator*() { return *_ptr; } private: T* _ptr; }; } int main() { Jam::unique_ptrDate up1(new Date); Jam::unique_ptrDate up2(new Date(2026,8,20)); Jam::unique_ptrDate up3(move(up1)); Jam::unique_ptrDate up4(move(up2)); up3 move(up4); return 0; }模拟实现shared_ptr:namespace Jam { templateclass T class shared_ptr { public: shared_ptr(T* ptr) :_ptr(ptr) ,_pcount(new int(1)) // 开辟一段空间来计算有多少个指针指向这个地址 { } shared_ptr(const shared_ptrT sp) { _ptr sp._ptr; _pcount sp._pcount; (*_pcount); } shared_ptrT operator(shared_ptrT sp) { if (this ! sp) { if (--(*_pcount) 0) { delete _ptr; _ptr sp._ptr; _pcount sp._pcount; } (*_pcount); } return *this; } ~shared_ptr() { if (--(*_pcount) 0) { delete _ptr; delete _pcount; _ptr nullptr; _pcount nullptr; } } T* operator-() { return _ptr; } T operator*() { return _ptr; } private: T* _ptr; int* _pcount; //用来计数防止多次析构 }; } int main() { Jam::shared_ptrDate sp1(new Date); Jam::shared_ptrDate sp2(new Date(2026, 8, 20)); Jam::shared_ptrDate sp3(sp1); Jam::shared_ptrDate sp4 sp2; return 0; }注我们可以发现这里new的都是一个对象然后用的是delete但是如果是一次开辟多个对象呢这时再用delete就会出错同时如果资源不是new出来的那么就无法释放所以我们还需要对unique_ptr和shared_ptr进行改进。1.4删除器删除器就是智能指针析构时用来释放资源的「可调用对象」仿函数 /lambda/ 函数。为什么要有删除器默认情况只能处理new / new[]出来的内存。 但是很多资源不是 new 出来malloc()分配内存要用free()释放fopen()打开文件要用fclose()关闭Windows 句柄CreateFile要用CloseHandle()关闭这种资源不能直接delete所以允许你传入自定义删除器告诉智能指针 “该怎么释放这个资源”写在模板尖括号内填类型→ 不加()写在函数 / 构造函数括号()内填对象→ 加()构造临时对象注通常是对new[]进行删除直接用库中的特化版本即可1.5完善shared_ptr模拟实现namespace Jam { templateclass T class shared_ptr { public: // 不传对象走这里 shared_ptr(T* ptr) :_ptr(ptr) ,_pcount(new int(1)) // 开辟一段空间来计算有多少个指针指向这个地址 { } // 传入lambda之类的对象走这个构造进行对[]删除 templateclass D shared_ptr(T* ptr, D del) :_ptr(ptr) ,_pcount(new int(1)) ,_del(del) { } shared_ptr(const shared_ptrT sp) { _ptr sp._ptr; _pcount sp._pcount; (*_pcount); } shared_ptrT operator(shared_ptrT sp) { if (this ! sp) { if (--(*_pcount) 0) { delete _ptr; _ptr sp._ptr; _pcount sp._pcount; } (*_pcount); } return *this; } ~shared_ptr() { if (--(*_pcount) 0) { _del(_ptr); delete _pcount; // 下面写不写都可以 _ptr nullptr; _pcount nullptr; } } T* operator-() { return _ptr; } T operator*() { return _ptr; } private: T* _ptr; int* _pcount; //用来计数防止多次析构 // 因为不知道外部传过来的对象是什么类型,可以是lambda函数指针仿函数 // 直接用包装器来包装不需要知道是什么类型 functionvoid(T*) _del [](T* ptr) {delete ptr; }; // 这里给缺省值是因为走第一个构造也要初始化_del,如果不给在析构的时候就出错 // 因为外部没传_ptr没有被释放 }; } int main() { Jam::shared_ptrDate sp1(new Date); Jam::shared_ptrDate sp2(new Date(2026, 8, 20)); Jam::shared_ptrDate sp3(sp1); Jam::shared_ptrDate sp4 sp2; auto delArrObj [](Date* ptr) {delete[] ptr; }; Jam::shared_ptrDate sp5(new Date[2], delArrObj); return 0; }1.6shared_ptr与weak_ptr1.6.1shared_ptr的循环引用问题shared_ptr大多数情况下管理资源非常合适支持RAII也支持拷贝。但是在循环引用的场景下会导致资源没得到释放内存泄漏所以我们要认识循环引用的场景和资源没释放的原因并且学会使用weak_ptr解决这种问题。如下图所述场景n1和n2析构后管理两个节点的引用计数减到1右边的节点什么时候释放呢左边节点中的_next管着呢_next析构后右边的节点就释放了。_next什么时候析构呢_next是左边节点的成员左边节点释放_next就析构了。左边节点什么时候释放呢左边节点由右边节点中的_prev管着呢_prev析构后左边的节点就释放了。_prev什么时候析构呢_prev是右边节点的成员右边节点释放_prev就析构了。至此逻辑上成功形成回旋镖似的循环引用谁都不会释放就形成了循环引用导致内存泄漏把ListNode结构体中的_next和_prev改成weak_ptrweak_ptr绑定到shared_ptr时不会增加它的引用计数_next和_prev不参与资源释放管理逻辑就成功打破了循环引用解决了这里的问题造成循环引用这时就需要用到weak_ptr了1.6.2weak_ptrweak_ptr 本身不拥有对象它是 shared_ptr 的 “观察者”不会增加引用计数。它不能直接解引用使用对象必须通过lock()转换成shared_ptr再用。 专门解决shared_ptr的循环引用内存泄漏问题。weak_ptr 能干什么、不能干什么✅可以绑定到一个shared_ptr管理的对象不增加 ref 计数判断对象是否还活着对象有没有已经被释放升级成shared_ptrlock()对象活着就得到 shared_ptr对象销毁返回空 shared_ptr❌不能不能直接*wp、wp-访问对象没有重载解引用不能管理资源不能做 RAII不会自动 delete 内存不能用unique_ptr初始化 weak_ptr只能配套shared_ptr总结四种智能指针的区别智能指针头文件所有权引用计数拷贝移动直接解引用* / -底层控制块主要用途核心注意点auto_ptrmemory独占所有权无❌拷贝会转移所有权拷贝后原对象置空支持✅可以无C98 老式独占智能指针C11 已经废弃不要用拷贝行为诡异不能放到容器彻底淘汰unique_ptrmemory独占所有权无❌禁止拷贝✅支持移动std::move✅可以无直接持有裸指针独占管理堆资源优先使用代替 auto_ptr数组支持unique_ptrT[]独占不能拷贝需要转移所有权必须 move不支持 weak_ptr 观察shared_ptrmemory共享所有权有引用计数✅支持拷贝 (计数 1)✅支持移动✅可以有控制块保存引用计数、删除器多个地方共享同一个对象生命周期多份持有者会产生循环引用内存泄漏不要用裸指针多次构造配合 make_shared 优先weak_ptrmemory无所有权仅观察者不改变计数✅拷贝✅支持移动❌不能直接* / -必须.lock()升级为 shared_ptr复用 shared_ptr 的控制块解决 shared_ptr 循环引用观察者模式、缓存只观察不持有对象只能绑定 shared_ptr不能配合 unique_ptrlock 后一定要判空多线程优先 lock 不要用 expired1.7C11和boost中智能指针的关系1.8内存泄漏1.8.1 什么是内存泄漏内存泄漏的危害1.8.2 如何检测内存泄漏了解Linux下内存泄漏检测Linux下几款C程序中的内存泄露检查工具_c内存泄露工具分析-CSDN博客window下检测windows下的内存泄露检测工具VLD使用_windows内存泄漏检测工具-CSDN博客1.8.3 如何避免内存泄漏

相关新闻

当 AI 不只是写代码:产研全生命周期一体化架构设计中的 TaoToken 统一 Key 通道

当 AI 不只是写代码:产研全生命周期一体化架构设计中的 TaoToken 统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 2:30:36 阅读更多 →
JavaScript 动画:从 setInterval 到 requestAnimationFrame 与可复用的 animate 时序框架(zh.javascript.info 实战指南)

JavaScript 动画:从 setInterval 到 requestAnimationFrame 与可复用的 animate 时序框架(zh.javascript.info 实战指南)

文档教程前端 【免费下载链接】zh.javascript.info 现代 JavaScript 教程(The Modern JavaScript Tutorial),以最新的 ECMAScript 规范为基准,通过简单但足够详细的内容,为你讲解从基础到高阶的 JavaScript 相关知识。…

2026/10/9 2:29:36 阅读更多 →
Tushare 股东增减持接口(stk_holdertrade)实战指南:股东增减持数据的获取、字段解析与事件驱动应用

Tushare 股东增减持接口(stk_holdertrade)实战指南:股东增减持数据的获取、字段解析与事件驱动应用

金融科技示例工程 【免费下载链接】ai_quant_trade Stock AI Trader: 1-stop platform for learning, sim & live trading. Covers: stock basics, strategies, LLMs, factor mining, ML/DL/RL, graph nets, HFT, C deploy & JoinQuant code. 股票AI操盘手:…

2026/10/9 2:29:36 阅读更多 →

最新新闻

Java共享自习室系统实战:Spring Boot、MyBatis与座位并发管理

Java共享自习室系统实战:Spring Boot、MyBatis与座位并发管理

简介:基于Java的共享自习室系统是一份面向高校学生和Java初学者的毕业设计完整项目,聚焦自习室预约与资源管理,从用户注册登录、自习室信息维护、预约规则设计到提醒通知均有实现,覆盖需求分析、数据库设计、接口开发、前端交互和…

2026/10/9 3:01:51 阅读更多 →
IP改编剧本生成:深度思考模型提示词工程实战指南

IP改编剧本生成:深度思考模型提示词工程实战指南

简介:本资源是一份面向影视编剧、AI内容创作者及IP开发从业者的专业指南,聚焦深度思考模型(如DeepSeek)在文学、动漫、游戏等IP改编场景中的提示词工程实践。文档系统梳理了提示词设计原则、优化策略、自动化脚本开发、与剧本写作…

2026/10/9 3:01:51 阅读更多 →
Hermes Agent vs OpenClaw 深度对比:架构、记忆、安全与生态选型指南(TaoToken 视角)

Hermes Agent vs OpenClaw 深度对比:架构、记忆、安全与生态选型指南(TaoToken 视角)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 3:01:51 阅读更多 →
Zed 编辑器:当你的光标会「读心术」,还养了一只猫当 UI 设计师——用 TaoToken 统一 Key 打通 Cursor 与 Zed 的 AI 补全

Zed 编辑器:当你的光标会「读心术」,还养了一只猫当 UI 设计师——用 TaoToken 统一 Key 打通 Cursor 与 Zed 的 AI 补全

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 3:01:51 阅读更多 →
starrtc-web私有云部署:WebRTC信令与媒体链路实践

starrtc-web私有云部署:WebRTC信令与媒体链路实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 3:01:51 阅读更多 →
DeepSeek+Coze AI获客智能体实战:从意图识别到工作流编排

DeepSeek+Coze AI获客智能体实战:从意图识别到工作流编排

简介:这份资源围绕DeepSeek与Coze组合,系统讲解从0到1搭建AI获客智能体的完整流程,面向传统行业中小老板、创业者与个人IP、销售运营人员,帮助解决短视频创作与获客难题。内容涵盖智能体定位与目标、业务流程梳理与痛点分析、场景…

2026/10/9 3:00:51 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 13:34:55 阅读更多 →