系列文章目录
提示:这里是系列文章的专栏
提示:以下是文章目录哦!
文章目录
目录
[1.1 裸指针异常场景下必然内存泄漏](#1.1 裸指针异常场景下必然内存泄漏)
[场景 1:int* array2 = new int10; 内存分配失败抛异常](#场景 1:int* array2 = new int[10]; 内存分配失败抛异常)
[场景 2:new 全部成功,进入 try 代码块,Divide 触发除零异常](#场景 2:new 全部成功,进入 try 代码块,Divide 触发除零异常)
[场景 3:代码全程无任何异常(new 成功、除法合法)](#场景 3:代码全程无任何异常(new 成功、除法合法))
[1.2 解决方案:RAII 思想 + 智能指针](#1.2 解决方案:RAII 思想 + 智能指针)
[二、RAII 设计思想 & 简易智能指针模拟实现](#二、RAII 设计思想 & 简易智能指针模拟实现)
[2.1 RAII 核心定义](#2.1 RAII 核心定义)
[2.2 简易 RAII 智能指针手写实现](#2.2 简易 RAII 智能指针手写实现)
[2.3 使用简易智能指针改造泄漏代码](#2.3 使用简易智能指针改造泄漏代码)
[1. 模板 template编辑](#1. 模板 template编辑)
[2. 私有成员 T* _ptr](#2. 私有成员 T* _ptr)
[3. 构造函数 SmartPtr (T* ptr) : _ptr (ptr)](#3. 构造函数 SmartPtr (T* ptr) : _ptr (ptr))
[4. 析构函数~SmartPtr ()(RAII 最核心)编辑](#4. 析构函数~SmartPtr ()(RAII 最核心)编辑)
[三、C++ 标准库四大智能指针完整使用](#三、C++ 标准库四大智能指针完整使用)
[3.1 auto_ptr(废弃,禁止使用)](#3.1 auto_ptr(废弃,禁止使用))
[3.2 unique_ptr 唯一智能指针(推荐优先使用)](#3.2 unique_ptr 唯一智能指针(推荐优先使用))
[3.3 shared_ptr 共享智能指针(引用计数)](#3.3 shared_ptr 共享智能指针(引用计数))
[3.4 自定义删除器(适配数组、文件、锁等非 new 资源)](#3.4 自定义删除器(适配数组、文件、锁等非 new 资源))
[3.5 weak_ptr 弱智能指针(辅助 shared_ptr,解决循环引用)](#3.5 weak_ptr 弱智能指针(辅助 shared_ptr,解决循环引用))
[weak_ptr 基础示例](#weak_ptr 基础示例)
[4.1 auto_ptr 模拟实现(管理权转移)](#4.1 auto_ptr 模拟实现(管理权转移))
[4.2 unique_ptr 模拟实现(禁用拷贝,仅支持移动)](#4.2 unique_ptr 模拟实现(禁用拷贝,仅支持移动))
[4.3 shared_ptr 模拟实现(引用计数核心)](#4.3 shared_ptr 模拟实现(引用计数核心))
[4.4 weak_ptr 极简模拟实现](#4.4 weak_ptr 极简模拟实现)
[五、shared_ptr 循环引用问题 & weak_ptr 解决方案](#五、shared_ptr 循环引用问题 & weak_ptr 解决方案)
[5.1 什么是循环引用?内存泄漏根源](#5.1 什么是循环引用?内存泄漏根源)
[六、shared_ptr 线程安全问题(PDF 第六节)](#六、shared_ptr 线程安全问题(PDF 第六节))
[6.1 两个层面的线程安全区分](#6.1 两个层面的线程安全区分)
[6.2 并发测试示例(存在数据竞争)](#6.2 并发测试示例(存在数据竞争))
[七、智能指针演进历史:Boost 库与 C++ 标准关系(PDF 第七节)](#七、智能指针演进历史:Boost 库与 C++ 标准关系(PDF 第七节))
[八、内存泄漏完整讲解(PDF 第八节)](#八、内存泄漏完整讲解(PDF 第八节))
[8.1 内存泄漏定义](#8.1 内存泄漏定义)
[8.2 内存泄漏危害](#8.2 内存泄漏危害)
[8.3 内存泄漏检测工具](#8.3 内存泄漏检测工具)
[8.4 避免内存泄漏两套方案](#8.4 避免内存泄漏两套方案)
前言
提示:这里可以添加本文要记录的大概内容:
传统 C++ 裸指针依靠手动 new/delete 管理堆内存,存在无法根治的缺陷:多分支逻辑、异常抛出场景极易遗漏释放代码,频繁诱发内存泄漏;资源生命周期管控分散,错误检测与资源回收代码高度耦合,底层函数需要兼顾报错与清理,业务逻辑臃肿杂乱。
智能指针依托 RAII 资源管理思想,将资源获取与释放绑定对象生命周期,完美分离资源申请逻辑 与错误处理逻辑:底层函数仅负责判断资源是否创建失败并抛出异常,无需关心上层如何处理回收;上层调用链依靠局部对象自动析构兜底释放资源,无论代码正常运行还是触发异常,堆内存、文件句柄、网络套接字等资源都能安全回收。
本文将从裸指针泄漏痛点切入,逐层拆解 RAII 核心原理、四大标准智能指针特性、手写底层模拟实现、自定义删除器、循环引用内存泄漏解决方案、多线程安全边界等全套知识点,结合完整可运行代码演示每一处语法与踩坑细节,帮你彻底搞懂智能指针完整体系。
提示:以下是本篇文章正文内容
一、为什么需要智能指针?裸指针的内存泄漏痛点
1.1 裸指针异常场景下必然内存泄漏
问题代码
cpp
#include <iostream>
using namespace std;
double Divide(int a, int b)
{
if (b == 0)
throw "Divide by zero condition!";
return (double)a / b;
}
void Func()
{
int* array1 = new int[10];
int* array2 = new int[10]; // 若new抛异常,array1直接泄漏
try
{
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
}
catch (...)
{
// 仅捕获除零异常,array2开辟失败时不会进这里
delete[] array1;
delete[] array2;
throw; // 重新抛出异常
}
// 正常流程释放
delete[] array1;
delete[] array2;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
泄漏场景分析
- 若
array2 = new int[10]开辟内存失败抛异常:函数直接终止,array1无任何释放逻辑,永久泄漏 - 若
Divide触发除 0 异常进入 catch,虽手动释放,但多层连续 new、多资源(文件、锁)场景下,try-catch 嵌套爆炸,代码臃肿难维护 - 手动管理资源依赖程序员记忆,分支越多越容易漏写
delete
场景 1:int* array2 = new int[10]; 内存分配失败抛异常
C++ 中new分配堆内存失败时,默认会抛出std::bad_alloc类型异常。
- 执行顺序:先执行
array1 = new int[10],堆内存开辟成功 - 再执行
array2 = new int[10],内存不足,触发throw bad_alloc - 此时函数
Func直接中断,不会进入下方的try-catch代码块 - 后果:
array1指向的堆数组没有任何delete[]释放,永久内存泄漏
场景 2:new 全部成功,进入 try 代码块,Divide 触发除零异常
流程:
array1、array2内存开辟成功- 进入
try内部,读取输入len、time,调用Divide(len, time) - 若
time == 0,Divide抛出字符串异常 - 立刻跳出
Divide,跳到当前匹配的catch(...)分支 catch(...)是万能捕获 ,能捕获任意类型异常:- 先执行
delete[] array1; delete[] array2;释放两块堆内存 throw;不带参数,表示重新抛出当前捕获到的异常 ,继续向上传递给外层main里的try-catch
- 先执行
catch执行完毕,不会走到函数末尾的正常释放代码
场景 3:代码全程无任何异常(new 成功、除法合法)
- try 内代码顺利执行完毕,无 throw
- 直接跳过
catch分支 - 执行函数末尾
delete[] array1; delete[] array2;,正常释放内存,无泄漏
细节补充:
catch(...):省略号表示捕获所有类型的异常,只能放在一组 catch 的最后;throw;空抛:只允许写在catch内部,作用是把当前捕获到的异常再次向上抛出,交给外层处理。
1.2 解决方案:RAII 思想 + 智能指针
裸指针手动释放缺陷无法靠 try-catch 根治,RAII 是标准化资源管理方案,智能指针是 RAII 最典型落地实现
二、RAII 设计思想 & 简易智能指针模拟实现
2.1 RAII 核心定义
RAII(Resource Acquisition Is Initialization,资源获取即初始化)
- 资源:堆内存、文件句柄、网络 socket、互斥锁等
- 核心逻辑:资源交给局部对象托管,对象构造时获取资源,对象生命周期结束(出作用域、异常退出)自动调用析构释放资源
- 优势:无论代码正常执行还是抛异常,对象必然析构,资源 100% 释放,彻底杜绝泄漏
2.2 简易 RAII 智能指针手写实现

2.3 使用简易智能指针改造泄漏代码

整体设计目标
前面裸指针代码最大问题:不管正常退出还是抛异常退出,都没法保证一定执行 delete \[\] 。 这个SmartPtr是极简 RAII 智能指针原型,设计目标只有 3 个:
- 接管裸指针的堆资源,对象生命周期结束自动释放内存,解决异常泄漏
- 重载运算符,用起来和普通裸指针完全一致,降低使用成本
- 封装裸指针,把资源释放逻辑收拢到类内部,不用使用者手动管理 delete
下面分模块逐个讲设计原因
1. 模板 template

为什么要用模板?
智能指针要管理任意类型堆资源:int、double、自定义Date、数组等
- 如果不写模板,只能写死
int*,只能管理 int 数组 - 模板泛型,
SmartPtr<int>管 int 数组、SmartPtr<Date>管自定义对象数组,一套代码适配所有类型,复用性拉满
2. 私有成员 T* _ptr

T* _ptr;
设计原因
- 存储托管的裸指针 :所有 new 出来的堆地址都存在
_ptr里,类内部统一持有资源 - 封装隐藏 :私有变量,外部无法直接修改
_ptr,防止外部手动delete _ptr造成二次释放、野指针 - 整个类所有成员函数都围绕这个指针做托管、访问、释放
3. 构造函数 SmartPtr (T* ptr) : _ptr (ptr)

核心设计思想:RAII「资源获取即初始化」
RAII 规则:创建对象时,立刻把资源交给对象托管
- 使用者
SmartPtr<int> sp(new int[10]);new开辟堆内存(获取资源)- 同步构造
SmartPtr局部对象,构造函数把堆指针存入_ptr,资源交给对象管理
- 为什么参数是
T* ptr? 接收new T[]返回的裸指针,建立对象和堆资源的绑定关系 - 这里简化实现没有加
explicit,标准库 unique_ptr/shared_ptr 会加 explicit 阻止隐式转换,本示例是简易 demo 所以省略
4. 析构函数~SmartPtr ()(RAII 最核心)

为什么析构里写 delete \[\]?解决裸指针泄漏的关键
C++ 规则:局部对象离开作用域时,一定会自动调用析构函数,无论函数是正常 return,还是中途 throw 异常栈展开
优化效果
无论new失败、Divide抛异常,局部对象sp1/sp2都会销毁,自动执行delete[],无内存泄漏,代码大幅简化
三、C++ 标准库四大智能指针完整使用
前置说明
- 头文件:
<memory>; - 分类:
auto_ptr(C++98废弃)、unique_ptr(C++11)、shared_ptr(C++11)、weak_ptr(C++11); - 共性:除
weak_ptr外均遵循 RAII,重载*/->模拟指针; - 差异核心:拷贝 / 移动逻辑、资源管理方式。
3.1 auto_ptr(废弃,禁止使用)
核心特性
拷贝时转移资源管理权,原对象置空悬空,后续访问崩溃,设计缺陷严重,C++11 标准废弃,企业编码规范禁用。
缺陷演示代码

3.2 unique_ptr 唯一智能指针(推荐优先使用)
核心特性
- 禁止拷贝构造、禁止拷贝赋值,编译器直接报错;
- 支持移动构造、移动赋值 (
std::move),移动后原智能指针置空; - 支持数组特化
unique_ptr<T[]>,原生适配new[],析构自动调用delete[]; - 支持自定义删除器,删除器类型作为模板参数
- 构造函数被
explicit修饰,禁止裸指针隐式转换; - 重载
operator*/operator->/operator[],像裸指针一样访问资源; - 支持
operator bool隐式转换,可直接if(up)判断是否持有资源。
示例代码
cpp
#include <iostream>
#include <memory>
using namespace std;
struct Date
{
int _year, _month, _day;
Date(int y=1,int m=1,int d=1):_year(y),_month(m),_day(d)
{
cout << "构造Date" << endl;
}
~Date()
{
cout << "析构Date" << endl;
}
};
int main()
{
// 1. 裸指针构造,explicit 禁止隐式转换
unique_ptr<Date> up1(new Date(2026,7,12));
// unique_ptr<Date> upErr = new Date; // 编译报错,explicit阻止隐式转换
// 2. 禁止拷贝,下面两行直接编译失败
// unique_ptr<Date> up2(up1);
// unique_ptr<Date> up3 = up1;
// 3. 移动所有权 std::move
unique_ptr<Date> up4(move(up1));
// 移动后 up1 为空,不再管理资源
if (!up1)
cout << "up1 已悬空,无资源" << endl;
// 4. 访问资源 * ->
cout << up4->_year << endl;
cout << (*up4)._month << endl;
// 5. 判空
if (up4)
cout << "up4 持有有效资源" << endl;
return 0;
}
运行逻辑:函数结束时 up4 析构,自动释放 Date 对象,无内存泄漏;即使函数中途抛异常,局部对象自动析构
3.3 shared_ptr 共享智能指针(引用计数)
核心特性
- 共享所有权 :支持拷贝、移动,底层堆上存储引用计数;
- 每拷贝一次计数 + 1,每析构一次计数 - 1;计数为 0 时释放资源;
- 提供
make_shared高效创建(一次堆内存分配,性能优于裸指针构造); - 删除器通过构造函数参数传入;
- 重载
operator bool,可直接 if 判断是否为空; - 构造函数
explicit修饰,禁止裸指针隐式转换。

cpp
#include <iostream>
#include <memory>
using namespace std;
// 模拟图中的红色资源对象
struct Resource
{
Resource()
{
cout << "创建一块堆资源" << endl;
}
~Resource()
{
cout << "释放一块堆资源" << endl;
}
};
int main()
{
// ========== 左侧图示逻辑 sp1、sp2 共享同一资源 ==========
shared_ptr<Resource> sp1(new Resource);
cout << "sp1 创建后,引用计数:" << sp1.use_count() << endl;
// sp2(sp1) 拷贝构造,和sp1共用资源、共用计数块,计数+1 → 2
shared_ptr<Resource> sp2(sp1);
cout << "sp2(sp1)拷贝后,引用计数:" << sp1.use_count() << endl;
cout << "sp1与sp2是否指向同一资源:" << (sp1.get() == sp2.get()) << endl;
cout << "sp1与sp2共用同一个引用计数块" << endl << endl;
// ========== 右侧图示逻辑 sp3 独立资源 ==========
shared_ptr<Resource> sp3(new Resource);
cout << "sp3独立创建资源,引用计数:" << sp3.use_count() << endl;
cout << "sp3和sp1资源地址不同,拥有独立的引用计数块" << endl;
return 0;
}
代码对应图形说明
sp1、sp2:两个shared_ptr内部黑色指针指向同一个红色资源;绿色指针指向同一个绿色计数块,数值为 2sp3:单独黑色箭头指向另一块红色资源,绿色箭头指向单独的计数块,数值为 1- 关键底层逻辑:
- 每一组共享资源,都会在堆上分配一块独立的引用计数控制块(绿色方框)
- 拷贝构造
shared_ptr<Resource> sp2(sp1)只会增加同一块计数,不会新建资源与计数 - 重新
new Resource会开辟全新资源 + 全新计数块,和之前完全隔离

运行结果:
3.4 自定义删除器(适配数组、文件、锁等非 new 资源)
智能指针默认析构调用delete,new[]/ 文件句柄 / 网络句柄需要自定义释放逻辑,支持 4 种删除器:仿函数、函数指针、lambda、std::function。
完整示例
cpp
#include <cstdio>
// 1. 函数指针删除器
template<class T>
void DeleteArrayFunc(T* ptr)
{
delete[] ptr;
}
// 2. 仿函数删除器
template<class T>
class DeleteArray
{
public:
void operator()(T* ptr)
{
delete[] ptr;
}
};
// 文件释放仿函数
class Fclose
{
public:
void operator()(FILE* ptr)
{
cout << "关闭文件:" << ptr << endl;
fclose(ptr);
}
};
int main()
{
// 方案1:标准数组特化(最简单)
unique_ptr<Date[]> up1(new Date[5]);
shared_ptr<Date[]> sp1(new Date[5]);
// 方案2:仿函数删除器
unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
shared_ptr<Date> sp2(new Date[5], DeleteArray<Date>());
// 方案3:函数指针删除器
unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);
shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);
// 方案4:lambda删除器
auto delArr = [](Date* ptr) { delete[] ptr; };
unique_ptr<Date, decltype(delArr)> up4(new Date[5], delArr);
shared_ptr<Date> sp4(new Date[5], delArr);
// 管理文件句柄(非堆内存资源)
shared_ptr<FILE> fp1(fopen("test.txt", "r"), Fclose());
shared_ptr<FILE> fp2(fopen("test.txt", "r"), [](FILE* f) { fclose(f); });
return 0;
}
关键区分知识点
1.unique_ptr删除器是模板参数,类型固化
- unique_ptr 删除器 = 模板参数 :删除器类型是
unique_ptr类型的一部分,编译期固定,栈上存储,零堆开销;不同删除器 = 完全不同的类型,不能互相赋值 / 转换

2.shared_ptr删除器是构造函数参数,存储在内部控制块,灵活多变
- shared_ptr 删除器 = 构造函数入参 :删除器存放在堆上的控制块,运行期绑定,不影响
shared_ptr<T>本身类型;同一个shared_ptr<T>可以持有不同删除器,互相赋值、拷贝无限制
3.5 weak_ptr 弱智能指针(辅助 shared_ptr,解决循环引用)
基础特性
- 不遵循 RAII,不能单独管理资源,无法通过裸指针构造;
- 仅能绑定
shared_ptr,不增加引用计数; - 无重载
*/->,不能直接访问资源; - 核心接口:
expired():判断绑定的资源是否已释放;lock():返回shared_ptr,安全访问资源,资源销毁则返回空智能指针;use_count():获取对应 shared_ptr 的引用计数。
weak_ptr 基础示例

四、四大智能指针底层模拟实现
4.1 auto_ptr 模拟实现(管理权转移)
cpp
namespace bit
{
template<class T>
class auto_ptr
{
public:
explicit auto_ptr(T* ptr = nullptr) : _ptr(ptr) {}
// 拷贝构造:转移所有权,原对象置空
auto_ptr(auto_ptr<T>& sp)
{
_ptr = sp._ptr;
sp._ptr = nullptr;
}
// 赋值重载
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap)
{
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
~auto_ptr()
{
delete _ptr;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
};
}
4.2 unique_ptr 模拟实现(禁用拷贝,仅支持移动)
cpp
namespace bit
{
template<class T>
class unique_ptr
{
public:
explicit unique_ptr(T* ptr = nullptr) : _ptr(ptr) {}
~unique_ptr() { delete _ptr; }
// 删除拷贝构造、拷贝赋值
unique_ptr(const unique_ptr<T>&) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
// 移动构造、移动赋值
unique_ptr(unique_ptr<T>&& sp)
{
_ptr = sp._ptr;
sp._ptr = nullptr;
}
unique_ptr<T>& operator=(unique_ptr<T>&& sp)
{
if (_ptr != sp._ptr)
{
delete _ptr;
_ptr = sp._ptr;
sp._ptr = nullptr;
}
return *this;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
};
}
4.3 shared_ptr 模拟实现(引用计数核心)
核心原理
- 资源指针
_ptr+ 堆上引用计数_pcount(不能用静态成员,每个资源独立计数); - 拷贝时计数 ++,析构 / 赋值时计数 --;计数为 0 时释放资源 + 释放计数;
- 内置通用删除器,支持自定义删除器
cpp
namespace bit
{
template<class T>
class shared_ptr
{
public:
explicit shared_ptr(T* ptr = nullptr)
: _ptr(ptr), _pcount(new int(1))
{}
// 带自定义删除器构造
template<class D>
shared_ptr(T* ptr, D del)
: _ptr(ptr), _pcount(new int(1)), _del(del)
{}
// 拷贝构造:共享计数
shared_ptr(const shared_ptr<T>& sp)
{
_ptr = sp._ptr;
_pcount = sp._pcount;
_del = sp._del;
++(*_pcount);
}
// 释放资源逻辑
void release()
{
if (_pcount && --(*_pcount) == 0)
{
_del(_ptr);
delete _pcount;
_ptr = nullptr;
_pcount = nullptr;
}
}
// 赋值重载
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
release();
_ptr = sp._ptr;
_pcount = sp._pcount;
_del = sp._del;
++(*_pcount);
}
return *this;
}
~shared_ptr() { release(); }
T* get() const { return _ptr; }
int use_count() const { return *_pcount; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
int* _pcount; // 标准库使用atomic<int>保证线程安全
function<void(T*)> _del = [](T* p) { delete p; };
};
}
4.4 weak_ptr 极简模拟实现
仅保存资源指针,不管理计数,无释放逻辑:
cpp
namespace bit
{
template<class T>
class weak_ptr
{
public:
weak_ptr() : _ptr(nullptr) {}
weak_ptr(const shared_ptr<T>& sp) : _ptr(sp.get()) {}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
private:
T* _ptr = nullptr;
};
}
五、shared_ptr 循环引用问题 & weak_ptr 解决方案
5.1 什么是循环引用?内存泄漏根源
双向链表节点互相使用shared_ptr指向对方,对象析构后双方引用计数均为 1,资源永远不会释放

泄漏逻辑拆解
- n1、n2 出作用域析构,各自计数从 2 变为 1;
- n1 资源依赖
n2->_prev持有,n2 资源依赖n1->_next持有; - 两个智能指针成员依附节点本身,节点不销毁则成员不析构,计数永远无法归零。
六、shared_ptr 线程安全问题(PDF 第六节)
6.1 两个层面的线程安全区分
- 引用计数操作 :多线程拷贝 / 析构
shared_ptr时,读写_pcount存在竞争,标准库使用atomic<int>原子变量保证计数线程安全;手写简易版int*计数会并发崩溃; - 托管资源本身:智能指针不保护指向的对象,多线程修改对象成员需要手动加锁。
6.2 并发测试示例(存在数据竞争)
cpp
#include <thread>
#include <mutex>
struct AA
{
int _a1 = 0;
};
int main()
{
bit::shared_ptr<AA> p(new AA);
const size_t n = 100000;
mutex mtx;
auto func = [&]()
{
for (size_t i = 0; i < n; ++i)
{
bit::shared_ptr<AA> copy(p); // 计数并发修改,简易实现无原子会崩溃
unique_lock<mutex> lk(mtx);
copy->_a1++; // 对象成员修改必须手动加锁
}
};
thread t1(func);
thread t2(func);
t1.join();
t2.join();
cout << p->_a1 << endl;
return 0;
}
七、智能指针演进历史:Boost 库与 C++ 标准关系(PDF 第七节)
- C++98:仅
auto_ptr,设计缺陷严重; - Boost 库:提供
scoped_ptr/scoped_array(对应 C++11 unique_ptr)、shared_ptr/shared_array、weak_ptr,是标准智能指针原型; - C++ TR1:引入
shared_ptr,非正式标准; - C++11:正式纳入
unique_ptr/shared_ptr/weak_ptr,废弃 auto_ptr,实现参考 Boost 设计。
八、内存泄漏完整讲解(PDF 第八节)
8.1 内存泄漏定义
程序丢失堆内存控制权,无法释放已申请内存,并非物理内存消失,而是逻辑上永久占用。
两种泄漏场景
- 裸指针忘记 delete、异常分支漏释放;
- shared_ptr 循环引用导致资源无法释放。
8.2 内存泄漏危害
- 短期运行程序:进程退出后操作系统回收所有内存,危害极小;
- 长期后台服务、服务器、嵌入式程序:内存持续上涨,可用内存枯竭,程序卡顿、崩溃。
演示代码(短期程序无明显危害)

8.3 内存泄漏检测工具
- Linux:valgrind;
- Windows:VLD 内存泄漏检测工具。
8.4 避免内存泄漏两套方案
- 事前预防(推荐):全程使用智能指针管理所有堆资源;自定义资源遵循 RAII 封装;
- 事后排查:上线前使用泄漏检测工具扫描代码。
九、四大智能指针选型总结
| 指针类型 | 拷贝 | 移动 | 引用计数 | 适用场景 |
| auto_ptr | 转移所有权(悬空) | 支持 | 无 | 废弃,禁止使用 |
| unique_ptr | 不支持 | 支持 | 无 | 独占资源,性能最优,优先选用 |
| shared_ptr | 支持 | 支持 | 有 | 需要多对象共享同一份资源 |
| weak_ptr | 不支持 | 支持 | 不增加计数 | 配合 shared_ptr,解决循环引用 |
|---|
