C++----智能指针
智能指针的使用场景分析
手动 new/delete 遇到异常,会 “忘删内存” 导致泄漏;智能指针能自动帮你删,不用你操心。
double Divide(int a , int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "Divide by zero condition !";
}
else
{
return (double)a / (double)b;
}
}
void Func()
{
int* array1 = new int[10];
int* array2 = new int[10]; // 抛异常呢
try
{
int len , time;
cin >> len >> time;
cout << Divide(len , time) << endl;
}
catch (...)
{
cout << "delete []" << array1 << endl;
cout << "delete []" << array2 << endl;
delete[] array1;
delete[] array2;
throw; // 异常重新抛出,捕获到什么抛出什么
}
// ...
cout << "delete []" << array1 << endl;
delete[] array1;
cout << "delete []" << array2 << endl;
delete[] array2;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e .what() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
这个代码看着运行基本没问题:
场景 1:完全不抛异常 ✅
- new array1 ✔
- new array2 ✔
- 正常计算 ✔
- 走到最后 delete array1、array2 ✔
场景 2:在 Divide 里抛异常(除 0)⚠
代码执行顺序:
- new array1 ✔
- new array2 ✔
- 进 try 里面
- 调用 Divide,直接抛异常!
- 立刻跳到 catch 里面
- catch 里 delete 两个数组 ✔
场景 3:在 new array2 的时候抛异常 ❌(真正的坑!)
执行顺序:
- new array1 成功了
- new array2 失败,直接抛异常!
- 直接跳出 Func 函数,根本进不去下面的 try catch!
结果:
- array1 申请了
- 永远没有机会 delete
- 内存泄漏!
需要智能指针解决
因为智能指针是:创建完立刻自动管理,哪怕下一行立刻抛异常,它也会自动释放!
unique_ptr<int[]> array1(new int[10]);
unique_ptr<int[]> array2(new int[10]);
new array2 抛异常:
- array1 已经是智能指针了
- 系统会自动 delete array1
- 绝对不会泄漏
#include <iostream>
#include <memory> // 智能指针头文件
using namespace std;
double Divide(int a, int b) {
if (b == 0) throw "Divide by zero condition !";
return (double)a / b;
}
void Func() {
// 用智能指针,完全不用写 delete!
unique_ptr<int[]> array1(new int[10]);
unique_ptr<int[]> array2(new int[10]);
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
// 函数结束 / 抛异常 → array1、array2 自动释放
}
int main() {
try {
Func();
}
catch (const char* errmsg) {
cout << errmsg << endl;
}
return 0;
}
RAII和智能指针的设计思路
什么是 RAII(Resource Acquisition Is Initialization)?
- 把资源交给对象管理
- 构造函数:拿到资源
- 析构函数:自动释放
- 不管正常退出还是异常栈展开,析构一定执行
RAII 就是:用对象托管资源,对象创建时自动拿资源,对象销毁时自动释放资源,无论代码正常结束还是异常崩溃,资源都不会泄露。
RAII 核心:
- 构造获取资源
- 析构释放资源
- 异常也能安全回收
- 不仅管内存,还能管锁、文件、套接字
智能指针遵循RAII 自动管理、自动释放内存,同时重载*、->、[]运算符后,使用方式和普通原生指针完全一致
//普通指针
int* p = new int;
*p = 10; // 用 * 取值
p->func(); // 用 -> 访问成员
p[0]; // 用 [] 访问数组元素
智能指针能替代普通指针,也支持:
*智能指针 → 取里面的值
智能指针-> → 访问成员
智能指针[] → 访问数组元素
模拟实现SmartPtr<T>
// 模板类:实现一个通用的智能指针(专门管理数组)
template<class T>
class SmartPtr
{
public:
// 构造函数
// RAII核心:用智能指针接管new出来的内存地址
SmartPtr(T* ptr)
:_ptr(ptr) // 初始化:把原生指针交给成员变量管理
{}
// 析构函数
// RAII核心:自动释放内存,不需要手动delete[]
// 重点:这个智能指针只用于数组,所以用 delete[]
~SmartPtr()
{
delete[] _ptr;
}
// 重载 * 运算符
// 作用:让智能指针可以像普通指针一样 *解引用
T& operator*()
{
return *_ptr;
}
// 重载 -> 运算符
// 作用:访问指针指向的对象成员
T* operator->()
{
return _ptr;
}
// 重载 [] 运算符
// 作用:让智能指针可以像数组一样用下标访问元素
T& operator[](size_t i)
{
return _ptr[i];
}
private:
T* _ptr; // 真正托管的原生指针
};
// 测试函数
void Func()
{
// 创建智能指针,自动管理两个数组
SmartPtr<int> sp1(new int[10]);
SmartPtr<int> sp2(new int[10]);
// 像普通数组/指针一样使用
sp1[0] = 10;
sp2[1] = 20;
// 函数结束:sp1、sp2自动调用析构函数 → 内存自动释放
}
int main()
{
Func();
return 0;
}
C++标准库智能指针的使用
1. 头文件
所有智能指针都在 <memory> 中:
2. auto_ptr(C++98 废弃指针)
致命缺陷:拷贝时会把资源管理权转移,原指针直接悬空,访问直接崩溃。
结论:C++11 后坚决弃用!
auto_ptr<Date> ap1(new Date);
auto_ptr<Date> ap2(ap1); // ap1 被掏空,变成野指针
// ap1->_year++; // 运行崩溃!
3. unique_ptr(重点)
- 同一时间只能有一个指针管理资源
- 禁止拷贝,从根源避免悬空问题
- 支持移动(转移权限)
unique_ptr<Date> up1(new Date);
// unique_ptr<Date> up2(up1); // 编译报错!禁止拷贝
unique_ptr<Date> up3(move(up1)); // 支持移动,移动后 up1 悬空
4. shared_ptr(共享智能指针)
- 多个指针共享同一块内存
- 底层用引用计数实现
- 拷贝一次计数 +1,析构一次计数 -1
- 计数为 0 时,才真正释放内存
shared_ptr<Date> sp1(new Date);
shared_ptr<Date> sp2(sp1); // 计数+1
shared_ptr<Date> sp3(sp2); // 计数+1
cout << sp1.use_count() << endl; // 输出 3
5.weak_ptr(弱智能指针)
- 依附
shared_ptr观察同一块内存,不拥有资源所有权 - 底层不参与强引用计数,只维护弱引用计数
- 拷贝 / 析构都不会改变 shared_ptr 强引用计数
- 自身永远不会主动释放内存,只在强计数归 0 后标记资源失效
- 不能直接
*/->访问对象,必须lock()转为 shared_ptr 使用
shared_ptr<Date> sp1(new Date);
weak_ptr<Date> wp1(sp1); // 绑定sp1,强计数不变
weak_ptr<Date> wp2(wp1); // 拷贝wp1,强计数依然不变
cout << sp1.use_count() << endl; // 输出 1
6.高级特性:删除器(解决数组 / 自定义资源释放)
1. 为什么需要删除器?
new数组 → 必须用 delete(智能指针默认)
智能指针默认用 delete 释放,如果管理的是:
new[]数组 → 必须用delete[]- 文件指针
FILE*→ 必须用fclose - 其他资源 → 自定义释放方式
这时候就需要自定义删除器。
2. 数组智能指针(最简单写法)
// 特化版本,自动用 delete[] 释放数组
unique_ptr<Date[]> up(new Date[5]);
shared_ptr<Date[]> sp(new Date[5]);
3. 三种自定义删除器
- 仿函数删除器
- 函数指针删除器
- Lambda 删除器(最常用)
// Lambda 表达式删除器(最简洁)
auto del = [](Date* ptr) { delete[] ptr; };
unique_ptr<Date, decltype(del)> up(new Date[5], del);
shared_ptr<Date> sp(new Date[5], del);
// 管理文件指针(自动 fclose)
shared_ptr<FILE>
spFile(fopen("test.txt", "r"), [](FILE* pf)
{ fclose(pf);}
);
7.其他重要特性
7.1make_shared(推荐创建 shared_ptr 的方式)
- 更高效、更安全
- 不需要写
new - 直接传构造参数
shared_ptr<Date> sp = make_shared<Date>(2024, 10, 1);
7.2可以直接在 if 中判断空指针
智能指针重载了 operator bool():
shared_ptr<Date> sp1;
if (!sp1) {
cout << "空指针" << endl;
}
7.3 不能隐式构造
unique_ptr/shared_ptr 构造函数用 explicit 修饰:
// 错误:拷贝初始化 = 触发隐式构造,智能指针构造是 explicit,禁止
shared_ptr<Date> sp1 = new Date;
// 正确:直接初始化,显式调用构造函数
shared_ptr<Date> sp2(new Date);
8.综合代码理解:
#include <iostream>
#include <memory>
using namespace std;
// 测试类
class Date {
public:
int _year, _month, _day;
Date(int y = 1, int m = 1, int d = 1) : _year(y), _month(m), _day(d) {}
~Date() { cout << "~Date()" << endl; }
};
// 主测试
int main() {
// ==================== 1. unique_ptr 独占指针 ====================
unique_ptr<Date> up1(new Date(2025, 1, 1));
up1->_year = 2026;
cout << "up1 year: " << up1->_year << endl;
// unique_ptr<Date> up2(up1); // 报错,不能拷贝
unique_ptr<Date> up2 = move(up1); // 可以移动
// ==================== 2. shared_ptr 共享指针 ====================
shared_ptr<Date> sp1 = make_shared<Date>(2025, 5, 20);
shared_ptr<Date> sp2(sp1);
shared_ptr<Date> sp3(sp1);
cout << "引用计数: " << sp1.use_count() << endl; // 3
sp1->_year = 9999;
cout << sp2->_year << endl; // 9999(共享同一块内存)
// ==================== 3. 数组智能指针 ====================
unique_ptr<Date[]> upArr(new Date[3]);
shared_ptr<Date[]> spArr(new Date[3]);
upArr[0]._year = 111;
// ==================== 4. Lambda 删除器 ====================
auto del = [](Date* p) {
cout << "自定义 delete[] 释放\n";
delete[] p;
};
unique_ptr<Date, decltype(del)> upCustom(new Date[2], del);
// ==================== 5. 管理文件指针 ====================
shared_ptr<FILE> fp(fopen("test.txt", "w"), [](FILE* pf) {
cout << "自动关闭文件\n";
fclose(pf);
});
return 0;
}
智能指针基础使用详解
四大智能指针核心对比
| 指针 | 版本 | 核心特点 | 拷贝 / 移动 | 适用场景 |
|---|---|---|---|---|
auto_ptr |
C++98 | 废弃!拷贝转移管理权,易悬空 | 支持拷贝(危险) | 绝对不要用 |
unique_ptr |
C++11 | 独占式指针,同一时间只有一个持有者 | 禁止拷贝,支持移动 | 不需要共享,最常用、最安全 |
shared_ptr |
C++11 | 共享式指针,引用计数管理 | 支持拷贝 + 移动 | 多个指针共享同一块内存 |
weak_ptr |
C++11 | 辅助指针,无所有权,不参与计数 | 支持拷贝 | 解决 shared_ptr 循环引用泄漏 |
模拟实现智能指针
auto_ptr
auto_ptr 是 C++ 早期智能指针,拷贝构造 / 赋值时直接转移资源管理权:原指针置空,新指针接管内存。
该设计存在致命安全缺陷,C++17 被标准彻底废弃,工程中绝对禁止使用,仅需理解历史设计坑点。
template<class T>
class auto_ptr
{
public:
auto_ptr(T*ptr):_ptr(ptr)
{}
//拷贝构造 管理权限转移!原对象指针置空
auto_ptr(auto_ptr<T> &ap):_ptr(ap._ptr)
{
ap._ptr = nullptr;//删除传入ap的地址
}
//赋值重载 ap1=ap2 管理权限转移!
auto_ptr<T>& operator=(auto_ptr<T>&ap)//ap2是传进来的
{
//this ap1的地址
if (this!=&ap)//防止自己给自己赋值
{
if (_ptr)
{
delete _ptr;
}
_ptr = ap._ptr;//传入ap的地址给赋值者
ap._ptr = nullptr;//删除传入ap的地址
}
return *this;//*this就是ap1
}
//重载*
T& operator*()
{
return *_ptr;
}
//重载->
T*operator->()
{
return _ptr;
}
~auto_ptr()
{
if (_ptr)
{
std::cout << "delete: " << _ptr << std::endl;
delete _ptr;
}
}
//重载<<
friend std::ostream& operator<<(std::ostream& out, const auto_ptr<T>& ap)
{
out << ap._ptr;
return out;
}
private:
T* _ptr;
};
//模板函数必须声明 + 定义放在一起,或者类内直接实现
//template<class T>
//std::ostream& operator<<(std::ostream& out, const auto_ptr<T>& ap)
//{
// out << ap._ptr;
// return out;
//}
struct Date
{
int _year = 2025;
};
// ====================== 测试 main 函数 ======================
int main()
{
// 测试1:构造指针
auto_ptr<Date> p(new Date);
std::cout << "p 原始地址:" << p << std::endl;
// 测试2:operator-> 重载
std::cout << "p->_year = " << p->_year << std::endl;
// 测试3:operator* 重载
std::cout << "(*p)._year = " << (*p)._year << std::endl;
std::cout << "------------"<< std::endl;
// 测试4:拷贝构造(管理权转移)
auto_ptr<Date> p1(p);// 拷贝后:ap1直接悬空变成空指针!ap2接管所有资源
// 下面代码直接崩溃!ap1已经是空野指针
//ap1->_year++;
std::cout << "p1 地址:" << p1 << std::endl;
std::cout << "p 拷贝后地址:" << p << std::endl; // 这里变成空!
std::cout << "p->_year = " << p1->_year << std::endl;
// 测试5:赋值重载
auto_ptr<Date> p2(new Date);
p2 = p1;
std::cout << "p2 赋值后地址:" << p2 << std::endl;
std::cout << "p1 赋值后地址:" << p1 << std::endl; // 空!
return 0;
}
拷贝后原指针彻底失效悬空,后续误访问直接程序崩溃,极易引发严重 bug,现代 C++ 完全淘汰。
unique_ptr
1. 核心概念
独占资源所有权:一份资源永远只能被 1 个unique_ptr管理
- 严格禁止拷贝构造、禁止拷贝赋值,从语法层面杜绝权限转移混乱
- 只支持移动语义,显式转移资源归属,移动后原指针悬空
- 无引用计数,开销极小、性能极高,90% 场景优先使用
template <class T>
class unique_ptr
{
public:
//构造 explicit禁止隐式类型转换
/*explicit unique_ptr(T&ptr):_ptr(ptr)*/
explicit unique_ptr(T* ptr) :_ptr(ptr)
{}
//禁止拷贝构造、拷贝赋值
unique_ptr(unique_ptr<T>& ap) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
//重载*
T& operator*()
{
return *_ptr;
}
//重载->
T* operator->()
{
return _ptr;
}
//移动构造:显示转移所有权
unique_ptr(unique_ptr<T>&& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
// 移动赋值:显式转移所有权
unique_ptr<T>& operator=(unique_ptr<T>&& ap)
{
if (this != &ap)
{
delete _ptr; // 释放自己原来的资源
_ptr = ap._ptr; // 接管资源
ap._ptr = nullptr;
}
return *this;
}
friend std::ostream& operator<<(std::ostream& out,const unique_ptr<T>&ap)
{
/* out << ap;
return ap;*/
out << ap._ptr;
return out;
}
//析构
~unique_ptr()
{
if (_ptr)
{
std::cout << "delete:" << _ptr << std::endl;
}
}
private:
T* _ptr;
};
struct Date
{
int _year = 2015;
};
int main()
{
unique_ptr<Date> p(new Date);
std::cout << "p的地址: " << p << std::endl;
// 测试 ->
std::cout << "p->_year: " << p->_year << std::endl;
// 测试 * 解引用
std::cout << "(*p)._year: " << (*p)._year << std::endl;
// ===================== 移动构造 =====================
unique_ptr<Date> p1(std::move(p));
std::cout << "p1->_year: " << p1->_year << std::endl;
std::cout << "p 移动后地址: " << p << std::endl;
// ===================== 移动赋值 =====================
unique_ptr<Date> p2(new Date);
p2 = std::move(p1); // ✅ 这里调用 移动赋值重载!
std::cout << "p2->_year: " << p2->_year << std::endl;
std::cout << "p1 移动后地址: " << p1 << std::endl;
return 0;
}
shared_ptr
1. 核心概念
多个智能指针共享同一份堆资源,通过引用计数管理生命周期:
- 每个资源单独在堆上 new 一份引用计数,绝对不能用
static静态成员- 静态计数是类所有对象共用,多个不同资源会错乱共用同一个计数,完全错误
- 新指针拷贝指向资源 → 引用计数
+1 - 指针析构离开作用域 → 引用计数
-1 - 只有引用计数减为 0,才真正释放堆资源 + 释放计数器本身
原理图解读:
- 左图:
sp1、sp2两个指针共用同一个资源、同一个计数器 → 引用计数 = 2 - 右图:只剩
sp3一个指针管理资源 → 引用计数 = 1
#include<iostream>
#include<functional>
//模拟shared_ptr
template <class T>
class shared_ptr
{
public:
//构造
shared_ptr(T* ptr=nullptr)
:_ptr(ptr),
_pcount(new int(1)),
_del([](T* ptr) { delete ptr; })
{}
////构造
//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>& ap)
:_ptr(ap._ptr),
_pcount(ap._pcount),
_del(ap._del)
{
(*_pcount)++;
}
//重载赋值运算符
shared_ptr<T>& operator=(const shared_ptr<T>& ap)
{
if (_ptr != ap._ptr)
{
release();//先解绑当前旧资源
_ptr = ap._ptr;
_pcount= ap._pcount;
++(*_pcount); // 新资源计数+1
_del = ap._del;
}
return *this;
}
//自定义析构函数
void release()
{
// 计数-1,只有归零才释放资源
if (--(*_pcount) == 0)
{
_del(_ptr);// 释放对象资源
delete _pcount;// 释放计数器本身
_ptr = nullptr;
_pcount = nullptr;
}
}
//获取指针
T* get()
{
return _ptr;
}
//获取pcpunt
int count()
{
return *(_pcount);
}
//重载*
T& operator*()
{
return *_ptr;
}
//重载->
T* operator->()
{
return _ptr;
}
//重载<<
friend std::ostream& operator<<(std::ostream& out, const shared_ptr<T>& ap)
{
out << ap._ptr;
return out;
}
~shared_ptr()
{
release();
}
private:
T* _ptr;
int* _pcount;
//默认delete删除器
std::function<void(T*)> _del;
//std::function<void(T*)> _del = [](T* ptr) { delete ptr; };
};
struct Date
{
int _year = 2015;
};
int main()
{
shared_ptr<Date> sp1(new Date);
shared_ptr<Date> sp2(sp1);
shared_ptr<Date> sp3(sp2);
std::cout << "当前引用计数:" << sp1.count() <<std:: endl; // 输出 3
// 所有指针指向同一块内存,修改同步生效
std::cout << sp1->_year << std::endl;
std::cout << sp2->_year << std::endl;
std::cout << sp3->_year << std::endl;
return 0;
}
weak_ptr
1. 核心概念
- 不增加引用计数,不参与资源生命周期管理
- 专门解决
shared_ptr循环 A 引用 B、B 引用 A,双方计数永远无法归零,造成永久内存泄漏问题 - 本文为极简入门实现,标准库完整版拥有
lock()安全接口,判断资源是否存活再取值 - 完整版需要单独抽离引用计数类,让 shared_ptr 和 weak_ptr 共用同一套计数结构
// 前置声明:weak_ptr要用到shared_ptr,提前声明
template <class T>
class shared_ptr;
//模拟实现 weak_ptr
template<class T>
class weak_ptr
{
public:
// 默认构造:初始化为空
weak_ptr()
:_ptr(nullptr),
_pcount(nullptr)
{}
// 用shared_ptr构造weak_ptr:只围观、不增加引用计数
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr),
_pcount(sp._pcount)
{}
// weak_ptr拷贝构造
weak_ptr(const weak_ptr<T>& wp)
:_ptr(wp._ptr),
_pcount(wp._pcount)
{}
// shared_ptr赋值给weak_ptr
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp._ptr;
_pcount = sp._pcount;
return *this;
}
// weak_ptr之间赋值
weak_ptr<T>& operator=(const weak_ptr<T>& sp)
{
_ptr = sp._ptr;
_pcount = sp._pcount;
return *this;
}
// 核心:lock 提升为shared_ptr,安全访问资源
shared_ptr<T> lock()
{
// 计数器有效 且 引用计数>0,资源存活
if (_pcount && *_pcount > 0)
{
return shared_ptr<T>(*this);
}
// 资源已销毁,返回空shared_ptr
return shared_ptr<T>();
}
// 判断资源是否已经失效/释放
bool expired() const
{
return _pcount == nullptr || *_pcount == 0;
}
// 析构:weak_ptr不释放资源,只单纯消亡
~weak_ptr() {}
private:
T* _ptr; // 指向托管资源
int* _pcount; // 指向堆上的引用计数
// 友元:允许shared_ptr访问weak_ptr私有成员
friend class shared_ptr<T>;
};
shared_ptr和weak_ptr
#include<iostream>
#include<functional>
// 前置声明:weak_ptr要用到shared_ptr,提前声明
template <class T>
class shared_ptr;
//模拟实现 weak_ptr
template<class T>
class weak_ptr
{
public:
// 默认构造:初始化为空
weak_ptr()
:_ptr(nullptr),
_pcount(nullptr)
{}
// 用shared_ptr构造weak_ptr:只围观、不增加引用计数
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr),
_pcount(sp._pcount)
{}
// weak_ptr拷贝构造
weak_ptr(const weak_ptr<T>& wp)
:_ptr(wp._ptr),
_pcount(wp._pcount)
{}
// shared_ptr赋值给weak_ptr
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp._ptr;
_pcount = sp._pcount;
return *this;
}
// weak_ptr之间赋值
weak_ptr<T>& operator=(const weak_ptr<T>& sp)
{
_ptr = sp._ptr;
_pcount = sp._pcount;
return *this;
}
// 核心:lock 提升为shared_ptr,安全访问资源
shared_ptr<T> lock()
{
// 计数器有效 且 引用计数>0,资源存活
if (_pcount && *_pcount > 0)
{
return shared_ptr<T>(*this);
}
// 资源已销毁,返回空shared_ptr
return shared_ptr<T>();
}
// 判断资源是否已经失效/释放
bool expired() const
{
return _pcount == nullptr || *_pcount == 0;
}
// 析构:weak_ptr不释放资源,只单纯消亡
~weak_ptr() {}
private:
T* _ptr; // 指向托管资源
int* _pcount; // 指向堆上的引用计数
// 友元:允许shared_ptr访问weak_ptr私有成员
friend class shared_ptr<T>;
};
//模拟实现 shared_ptr 引用计数版本
template <class T>
class shared_ptr
{
public:
// 构造函数:裸指针构造,引用计数初始化为1
shared_ptr(T* ptr = nullptr)
:_ptr(ptr),
_pcount(new int(1)),
_del([](T* ptr) { delete ptr; }) // 默认删除器:delete释放
{}
// 自定义删除器构造:适配数组、malloc、文件等特殊资源
template<class D>
shared_ptr(T* ptr, D del)
:_ptr(ptr),
_pcount(new int(1)),
_del(del)
{}
// 关键新增:接收weak_ptr的构造函数 给lock()调用
shared_ptr(const weak_ptr<T>& wp)
:_ptr(wp._ptr),
_pcount(wp._pcount)
{
if (_pcount != nullptr)
{
++(*_pcount); // 提升为shared_ptr,计数+1
}
}
// 拷贝构造:共享资源,引用计数+1
shared_ptr(const shared_ptr<T>& ap)
:_ptr(ap._ptr),
_pcount(ap._pcount),
_del(ap._del)
{
(*_pcount)++;
}
// 赋值重载:两个shared_ptr互相赋值
shared_ptr<T>& operator=(const shared_ptr<T>& ap)
{
// 不是同一个资源才处理
if (_ptr != ap._ptr)
{
release(); // 先释放当前旧资源的计数
_ptr = ap._ptr; // 接管新资源指针
_pcount = ap._pcount; // 接管新计数器
++(*_pcount); // 新资源计数+1
_del = ap._del; // 同步删除器
}
return *this;
}
// 资源释放核心函数:计数--,计数为0才真正释放内存
void release()
{
if (--(*_pcount) == 0)
{
_del(_ptr); // 调用删除器释放对象
delete _pcount; // 释放堆上的引用计数
_ptr = nullptr; // 置空防止野指针
_pcount = nullptr;
}
}
// 获取原生裸指针
T* get()
{
return _ptr;
}
// 获取当前引用计数
int count() const
{
return *(_pcount);
}
// 重载* 解引用
T& operator*()
{
return *_ptr;
}
// 重载-> 访问成员
T* operator->()
{
return _ptr;
}
// 支持if(sp) 判断智能指针是否为空
explicit operator bool() const
{
return _ptr != nullptr;
}
// 重载<< 打印托管指针地址
friend std::ostream& operator<<(std::ostream& out, const shared_ptr<T>& ap)
{
out << ap._ptr;
return out;
}
// 析构:对象生命周期结束,调用release减少计数
~shared_ptr()
{
release();
}
private:
T* _ptr; // 管理的堆资源指针
int* _pcount; // 堆上引用计数(多个智能指针共享)
std::function<void(T*)> _del;// 统一删除器,支持自定义释放
// 友元:让weak_ptr访问shared_ptr私有成员
friend class weak_ptr<T>;
};
// ====================== 测试代码 =========================
// 测试实体类
struct Date
{
int _year = 2025;
};
//测试1:weak_ptr 只观察,**不增加引用计数**
void test1()
{
std::cout << "===== test1: weak_ptr 不增加计数 =====" << std::endl;
shared_ptr<Date> sp(new Date);
std::cout << "sp use_count: " << sp.count() << std::endl;
weak_ptr<Date> wp = sp; // weak_ptr绑定shared_ptr,计数不变
std::cout << "wp 绑定后 sp use_count: " << sp.count() << std::endl;
std::cout << std::endl;
}
//测试2:检测资源是否失效(解决野指针)
void test2()
{
std::cout << "===== test2: 资源失效检测 =====" << std::endl;
weak_ptr<Date> wp;
// 局部作用域:sp出作用域自动销毁
{
shared_ptr<Date> sp(new Date);
wp = sp;
std::cout << "sp 存活,wp expired: " << std::boolalpha << wp.expired() << std::endl;
}
// 此时sp已析构,资源释放
std::cout << "sp 已释放,wp expired: " << std::boolalpha << wp.expired() << std::endl;
std::cout << std::endl;
}
//测试3:lock() 安全提升为shared_ptr,合法访问资源
void test3()
{
std::cout << "===== test3: lock 提升 =====" << std::endl;
weak_ptr<Date> wp;
{
shared_ptr<Date> sp(new Date);
wp = sp;
shared_ptr<Date> sp2 = wp.lock(); // 提升
if (sp2) // 资源有效才访问
{
std::cout << "lock 成功,year: " << sp2->_year << std::endl;
}
}
shared_ptr<Date> sp3 = wp.lock();
if (!sp3)
{
std::cout << "资源已释放,lock 失败" << std::endl;
}
}
int main()
{
test1();
test2();
test3();
return 0;
}
智能指针的原理
shared_ptr和weak_ptr
shared_ptr 的完美与缺陷
完美:
- 支持 RAII,自动释放资源
- 支持拷贝,共享资源,工程最常用
致命坑:循环引用
循环引用

图解过程
- 初始状态:n1、n2 各指向一个节点,引用计数都是 1。
- 互相指向:
- n1->_next = n2 → 右边节点引用计数变成 2
- n2->_prev = n1 → 左边节点引用计数变成 2
- 离开作用域:n1、n2 销毁,引用计数各减 1 → 都变成 1。
- 死锁形成:
- 左边节点要释放,必须等 _prev(n2)销毁
- 右边节点要释放,必须等 _next(n1)销毁
- 谁都不释放 → 内存泄漏!
代码解释:
struct ListNode
{
int _data;
//这里是 shared_ptr,形成循环
shared_ptr<ListNode> _next;
shared_ptr<ListNode> _prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
shared_ptr<ListNode> n1(new ListNode);
shared_ptr<ListNode> n2(new ListNode);
cout << n1.use_count() << endl; // 1
cout << n2.use_count() << endl; // 1
n1->_next = n2; // n2 引用+1
n2->_prev = n1; // n1 引用+1
cout << n1.use_count() << endl; // 2
cout << n2.use_count() << endl; // 2
return 0;
}
weak_ptr 解决方案
1. 核心思想
weak_ptr 是 shared_ptr 的辅助指针:
- 不增加引用计数
- 不参与 RAII 管理
- 不影响资源释放
2.修正后代码
struct ListNode
{
int _data;
// 🔥 改成 weak_ptr,打破循环
weak_ptr<ListNode> _next;
weak_ptr<ListNode> _prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
shared_ptr<ListNode> n1(new ListNode);
shared_ptr<ListNode> n2(new ListNode);
cout << n1.use_count() << endl; // 1
cout << n2.use_count() << endl; // 1
n1->_next = n2; // 不增加计数
n2->_prev = n1; // 不增加计数
cout << n1.use_count() << endl; // 1
cout << n2.use_count() << endl; // 1
return 0;
}
执行过程
- n1、n2 销毁 → 引用计数各减 1 → 都变成 0。
- 引用计数为 0 → 自动释放资源 → 析构函数被调用。
- 完美解决泄漏!
3.weak_ptr再次介绍
weak_ptr 三大核心特点
- 不支持 RAII不能自己管理资源,不能直接 new 对象。
- 不支持直接访问资源没有重载
*和->,不能直接用 wp->xxx。- 只能绑定 shared_ptr绑定后不增加引用计数 → 专门解决循环引用。
想访问资源怎么办?
必须调用
wp.lock()
- 返回一个 shared_ptr
- 如果资源还活着 → 返回有效指针
- 如果资源死了 → 返回空指针这样访问绝对安全!
代码解释:
、int main()
{
// 1. 创建 sp1 管理字符串 "111111",引用计数 = 1
std::shared_ptr<string> sp1(new string("111111"));
// 2. sp2 拷贝 sp1,引用计数 = 2
std::shared_ptr<string> sp2(sp1);
// 3. weak_ptr 绑定 sp1
// 🔥 重点:不增加引用计数!计数仍然是 2
std::weak_ptr<string> wp = sp1;
// 4. 资源没过期 → false(0)
cout << wp.expired() << endl; // 输出:0
// 5. 引用计数是 2
cout << wp.use_count() << endl; // 输出:2
// ---------------------------------------------------
// 6. sp1 指向新资源 "222222"
// 原来的 "111111" 计数 -1 → 变成 1
sp1 = make_shared<string>("222222");
// 7. 资源 "111111" 还活着 → false(0)
cout << wp.expired() << endl; // 输出:0
// 8. 计数 = 1
cout << wp.use_count() << endl; // 输出:1
// ---------------------------------------------------
// 9. sp2 也指向新资源 "333333"
// 原来的 "111111" 计数 -1 → 变成 0 → 释放!
sp2 = make_shared<string>("333333");
// 10. 资源 "111111" 已经死了 → true(1)
cout << wp.expired() << endl; // 输出:1
// 11. 计数 = 0
cout << wp.use_count() << endl; // 输出:0
// ---------------------------------------------------
// 12. wp 重新绑定 sp1(指向 "222222")
wp = sp1;
// 13. 调用 lock() 获取 shared_ptr
// 资源活着 → sp3 有效
auto sp3 = wp.lock();
// 14. 没过期 → false
cout << wp.expired() << endl; // 输出:0
// 15. 计数 +1 → 变成 2
cout << wp.use_count() << endl; // 输出:2
// ---------------------------------------------------
// 16. 通过 sp3 修改资源
*sp3 += "###";
// 17. 输出 sp1:222222###
cout << *sp1 << endl; // 输出:222222###
return 0;
}
shared_ptr 线程安全
- 引用计数的加减是线程安全的(底层用原子操作,自动安全)
- 智能指针指向的对象 不是线程安全的(需要你自己加锁)
- 多个线程拷贝 / 析构 shared_ptr 安全
- 多个线程修改指向的对象 不安全
代码解释:
struct AA
{
int _a1 = 0;
int _a2 = 0;
~AA()
{
cout << "~AA()" << endl;
}
};
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++;
copy->_a2++;
}
};
// 两个线程同时拷贝、使用智能指针
thread t1(func);
thread t2(func);
t1.join();
t2.join();
// 最终结果应该都是 200000
cout << p->_a1 << endl;
cout << p->_a2 << endl;
// 计数回到 1(只有p自己)
cout << p.use_count() << endl;
return 0;
}
//解释
bit::shared_ptr copy(p) 线程安全
因为引用计数是原子操作,多个线程拷贝不会错乱。
copy->_a1++ 不安全
所以必须加锁,否则结果会错误。
内存泄漏
1.什么是内存泄漏?
该释放的内存没释放 → 白白占着空间 → 就是内存泄漏。
常见原因:
- 忘记
delete - 抛异常了,
delete没执行到 - 指针丢了,找不到要释放的内存
不是内存坏了,是没人管、没人释放了。
2.危害是什么?
- 短程序:运行一下就关,泄漏一点没事,进程结束系统自动回收内存。
- 长期运行程序(服务器、后台、游戏、操作系统):泄漏越来越多 → 内存越来越少 → 程序越来越卡 → 最后卡死、崩溃。
3.内存泄漏代码例子
//最基础的忘记 delete
void func()
{
// 申请内存
int* p = new int(10);
// 用完没释放!
// delete p;
}
int main()
{
func();
return 0;
}
泄漏原因:只 new 不 delete。
//抛异常导致无法执行 delete(经典)
void func()
{
int* p = new int[10];
// 这里抛异常
if (1)
throw "出错啦";
// 这句根本执行不到!
delete[] p;
}
int main()
{
try {
func();
}
catch (...) {}
return 0;
}
泄漏原因:异常跳转,释放代码没跑到
//重复覆盖指针(找不到原来的内存)
int main()
{
int* p = new int(10);
// 又申请一块内存
p = new int(20);
// 第一块内存地址丢了!
// 永远释放不了了
delete p;
return 0;
}
泄漏原因:原来的内存地址被覆盖,找不到了。
4.怎么检测泄漏?(了解)
- Windows:用 VLD 工具
- Linux:用 valgrind 等工具
- 作用:告诉你哪一行
new没delete
5.怎么避免泄漏?(重点)
- 用智能指针(shared_ptr/unique_ptr)自动释放,永不泄漏,异常也安全。
- 遵循 RAII:资源交给对象管理
- 少用裸指针
new/delete - 工具定期检查
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐




所有评论(0)