智能指针的使用场景分析

手动 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)⚠

代码执行顺序:

  1. new array1 ✔
  2. new array2 ✔
  3. 进 try 里面
  4. 调用 Divide,直接抛异常!
  5. 立刻跳到 catch 里面
  6. catch 里 delete 两个数组 ✔

场景 3:在 new array2 的时候抛异常 ❌(真正的坑!)

执行顺序:

  1. new array1 成功了
  2. new array2 失败,直接抛异常!
  3. 直接跳出 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)?

  1. 资源交给对象管理
  2. 构造函数:拿到资源
  3. 析构函数:自动释放
  4. 不管正常退出还是异常栈展开,析构一定执行

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. 核心概念

多个智能指针共享同一份堆资源,通过引用计数管理生命周期:

  1. 每个资源单独在堆上 new 一份引用计数,绝对不能用static静态成员
    • 静态计数是类所有对象共用,多个不同资源会错乱共用同一个计数,完全错误
  2. 新指针拷贝指向资源 → 引用计数+1
  3. 指针析构离开作用域 → 引用计数-1
  4. 只有引用计数减为 0,才真正释放堆资源 + 释放计数器本身

原理图解读:

  • 左图:sp1sp2 两个指针共用同一个资源、同一个计数器 → 引用计数 = 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_ptrweak_ptr

shared_ptr 的完美与缺陷

完美:

  • 支持 RAII,自动释放资源
  • 支持拷贝,共享资源,工程最常用

致命坑:循环引用

循环引用

图解过程

  1. 初始状态:n1、n2 各指向一个节点,引用计数都是 1。
  2. 互相指向
    • n1->_next = n2 → 右边节点引用计数变成 2
    • n2->_prev = n1 → 左边节点引用计数变成 2
  3. 离开作用域:n1、n2 销毁,引用计数各减 1 → 都变成 1。
  4. 死锁形成
    • 左边节点要释放,必须等 _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 三大核心特点

  1. 不支持 RAII不能自己管理资源,不能直接 new 对象
  2. 不支持直接访问资源没有重载 *->不能直接用 wp->xxx
  3. 只能绑定 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 等工具
  • 作用:告诉你哪一行 newdelete

5.怎么避免泄漏?(重点)

  1. 用智能指针(shared_ptr/unique_ptr)自动释放,永不泄漏,异常也安全。
  2. 遵循 RAII:资源交给对象管理
  3. 少用裸指针 new/delete
  4. 工具定期检查

更多推荐