【C++ 智能指针全解析】从内存泄漏痛点到 RAII + unique/shared/weak_ptr 手撕实现

【C++ 智能指针全解析】从内存泄漏痛点到 RAII + unique/shared/weak_ptr 手撕实现

作者 :yuuki233233
时间: 2026年3月6日
目标:德国CS本科 + 特斯拉软件工程师

代码仓库:https://github.com/yuuki233233/cpp-learning-journey

CSDN 主页:https://blog.csdn.net/yuuki233233

欢迎点赞、收藏、评论,一起卷现代 C++!

智能指针的使用及原理

智能指针使用场景分析

问题引入:

case 1:无异常

  • try 中创建 array1、array2array1,array2 都创建成功,没抛出异常,array 正常销毁。(无内存泄漏)
    case 2:有异常
  • try 中创建 array1、array2array1 先发生异常,因创建失败而没申请空间,因先发生异常而没创建 array2(无内存泄漏)
  • try 中创建 array1、array2array2 发生异常,此时 array1 申请了空间,因 array2 发生异常 被 catch 捕捉,这里要另外写一套捕获释放逻辑,很麻烦
    因为 case 2 中可能会因异常而导致内存泄漏,需要我们手动再写一遍 catch 捕捉,就此产生了智能指针帮我们管理内存。
cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

double Divide(int a, int b)
{
	if (b == 0)
	{
		throw "Divide by zero condition!";
	}
	else
	{
		return (double)a / (double)b;
	}
}

void Func()
{
	// 如果发生除0错误抛出异常,另外下面的 array1 和 array2 没有得到释放
	// 捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再重新抛出去
	// 但如果 array2 抛异常,就还需要写异常捕获释放逻辑,这里更好的办法是智能指针
	int* array1 = new int[10];
	int* array2 = new int[10];

	try
	{
		int len, time;
		cin >> len >> time;
		cout << Divide(len, time) << endl;
	}
	catch (...) // 需捕捉异常,里面要 delete,外层也要 delete,这很麻烦
	{
		cout << "delete []" << array1 << endl;
		cout << "delete []" << array2 << endl;

		delete[] array1;
		delete[] array2;

		throw;
	}
	
	// 外层 delete
	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 << "Unkown" << endl;
	}

	return 0;
}

RAII:资源获取即初始化(Resource Acquisition Is Initialization)

在上面的例子中,我们发现手动 new/delete 最大的问题在于:

  • 有可能因为程序的逻辑原因,没有执行到释放,比如前面 new 了,后面 delete 了,中间就可能抛异常
  • 异常发生时,delete[] array1delete[] array2 可能得不到执行 → 内存泄漏。
  • 代码需要在 trycatch 里都写释放逻辑 → 重复、容易出错。
  • 连续的抛异常可能会让我们写 newmalloc 非常的难受,比如函数再复杂一点(多层嵌套、多个资源),手动管理几乎不可能不出错。
    所以引入智能指针来解决上述的问题:C++ 的解决方案就是 RAII(资源获取即初始化)

总结:获取到资源就别自己主动去释放,很容易出现问题。这时候就交给一个对象去管理,这个对象把指针给存起来,出了作用域就一定会调用析构函数,无论是抛异常还是正常结束,总会去释放的

RAII 的核心思想(一句话记住)

资源(内存、文件、锁、socket 等)的生命周期对象的生命周期绑定:

  • 对象构造时获取资源(new、open、lock)
  • 对象析构时自动释放资源(delete、close、unlock)

这样即使抛出异常,栈展开也会自动调用对象的析构函数,资源不会泄漏。

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

// 智能指针
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{ }

	~SmartPtr()
	{
		cout << "delete[]" << _ptr << endl;
		delete[] _ptr;
	}

private:
	T* _ptr;
};

double Divide(int a, int b)
{
	if (b == 0)
	{
		throw "Divide by zero condition!";
	}
	else
	{
		return (double)a / (double)b;
	}
}

void Func()
{
	// 智能指针:自动初始化、自动析构,不需重复捕获异常
	SmartPtr<int> sp1 = new int[10];
	SmartPtr<int> sp2 = new int[10];
	SmartPtr<int> sp3 = new int[10];
	SmartPtr<int> sp4 = new int[10];

	int len, time;
	cin >> len >> time;
	cout << Divide(len, time) << endl;
}

int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "Unkown" << endl;
	}

	return 0;
}

如果是自定义类型或 pair 类型 ,这时就需要重载 operator->()、operator*()、operator[]

cpp 复制代码
// 智能指针
template<class T>
class SmartPtr
{
public:
	// RAII
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{
	}

	~SmartPtr()
	{
		cout << "delete[] " << _ptr << endl;
		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<pair<int, int>> sp5 = new pair<int, int>[10];

	sp1[5] = 50;		// operator[]
	sp5->first = 1;		// operator->()
	sp5->second = 2;	// operator->()

	int len, time;
	cin >> len >> time;
	cout << Divide(len, time) << endl;
}

C++标准库智能指针

  • auto_ptr 是 C++98 设计出来的智能指针,特点:拷贝时把对象资源转移给拷贝对象,被拷贝对象为空(强烈不建议使用)
  • C++11 提供了三种智能指针(在 memory 头文件里),它们都是 RAII 的典型应用,分别是 unique_ptr、shared_ptr、weak_ptr

unique_ptr 的使用和原理以及模拟实现

unique_ptr 使用
  • unique_ptr:不支持拷贝,只支持移动 (不需要拷贝的场景)
cpp 复制代码
  unique_ptr<Date> up1(new Date);
  // 不支持拷贝构造,支持移动构造
  //unique_ptr<Date> up2(up1);
  unique_ptr<Date> up3(move(up1));
unique_ptr 原理
  • auto_ptr 的思路是拷贝是转移资源管理权给被拷贝对象,unique_ptr 的思路是不支持拷贝
unique_ptr 模拟实现
cpp 复制代码
// 模拟实现 unique_ptr
namespace yuuki
{
	template<class T>
	class unique_ptr
	{
	public:
		// 默认构造
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{ }

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete[]:" << _ptr << endl;
				delete _ptr;
			}
		}

		// 支持 自定义类型 和 pair类型
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		// unique_ptr 不支持左值,需 delete
		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

		// unique_ptr 支持右值
		// 为什么移动构造没有 delete[] _ptr,因为 sp4 是新建的,_ptr 没有申请内存
		unique_ptr(unique_ptr<T>&& sp)
			:_ptr(sp._ptr)
		{
			sp._ptr = nullptr;
		}

		unique_ptr<T>& operator=(unique_ptr<T>&& sp)
		{
			// sp1 = sp3,sp1 中的 _ptr 存在数据,需要 delete
			delete _ptr;
			_ptr = sp._ptr;
			sp._ptr = nullptr;
		}

	private:
		T* _ptr;
	};
}

shared_ptr 的使用和原理以及模拟实现

shared_ptr 使用
  • shared_ptr:支持拷贝和移动,底层是用引用计数的方式实现的。(需要拷贝的场景)
cpp 复制代码
  shared_ptr<Date> sp1(new Date);
  // 支持拷贝构造和移动构造
  shared_ptr<Date> sp2(sp1);
  shared_ptr<Date> sp3(sp2); 
  // sp3 先析构, --计数 -> sp2
  // sp2 再析构, --计数 -> sp1
  // sp1 析构, --计数 -> 0,完成整个析构
shared_ptr 原理
  • shared_ptr
    • 共享:单个对象不共享计数 ,如默认构造 sp1 和 默认构造 sp3;相同对象的拷贝构造对象共用同个计数,如 默认构造 sp1 拷贝构造 sp2(sp1)。
    • count 引用计数:
      • 常量(每一个对象有不同的引用计数):默认构造 sp1 -> int = 1,拷贝构造 sp2(sp1) -> int = 2,两个 int 不共享,但因为 sp2 是拷贝构造,第一个 int++,最终 sp1 -> int = 2。sp2 析构 int = 2 -> int = 1,sp1 析构 int = 2 -> int = 1这两个对象都不共享同一个 int
      • 静态(每一个对象有共同的引用计数):静态属于这个类的所有对象 。上面的情况不变,额外添加一个默认构造 sp3,原本 sp3 -> int = 1,但因为用的是静态成员变量,sp3 共享 sp1 和 sp2 的 int,结果 sp3 -> int = 3
      • 指针:**动态开辟,来一份资源 new 一个计数,两个对象同时指向一份资源,如:sp1 和 sp2(sp1)
shared_ptr 模拟实现
cpp 复制代码
#include<iostream>
#include<memory>
using namespace std;

class Date
{
public:
	Date(int year = 2026, int month = 3, int day = 4)
		:_year(year)
		,_month(month)
		,_day(day)
	{ }

	Date(const Date& d)
		:_year(d._year)
		,_month(d._month)
		,_day(d._day)
	{ }

	Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return *this;
	}

	~Date()
	{
		cout << "~Date()" << endl;
	}

//private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

namespace yuuki
{
	template<class T>
	class shared_ptr
	{
	public:
		// sp1 默认构造生成 _pcount 计数为 1
		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{ }

		// sp2(sp1) _pcount 计数需++
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
		{
			(*_pcount)++;
		}

		//// sp1 = sp4  sp5 = sp4  sp1 = sp2
		//shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		//{
		//	if (_ptr != sp._ptr)
		//	{
		//		if (_ptr)
		//		{
		//			delete _ptr;
		//			delete _pcount;
		//		}
		//		_ptr = sp._ptr;
		//		_pcount = sp._pcount;
		//		(*_pcount)++;
		//	}
		//	else
		//	{
		//		return *this;
		//	}
		//	
		//	return *this;
		//}

		// sp1 = sp4  sp5 = sp4  sp1 = sp2
		shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
			if (this != &sp) {  // 防止自赋值
				if (_ptr != sp._ptr) {  // 防止指向同一块内存(可选,但好习惯)
					if (--(*_pcount) == 0) {  // 计数减到0才释放
						delete _ptr;
						delete _pcount;
					}
					_ptr = sp._ptr;
					_pcount = sp._pcount;
					++(*_pcount);  // 新增引用计数
				}
			}
			return *this;
		}

		// 每次析构 sp2 _pcount 计数需--
		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		// 修正 shared_ptr 中 use_count 的声明
		int use_count()
		{
			return (*_pcount);
		}

	private:
		T* _ptr;
		int* _pcount;
	};
}

int main()
{
	yuuki::shared_ptr<Date> sp1(new Date);
	yuuki::shared_ptr<Date> sp2(sp1);
	// 调用拷贝构造,因为 sp3 不存在
	yuuki::shared_ptr<Date> sp3 = sp2;
	cout << "sp1.use_count():" << sp1.use_count() << endl; // 3

	yuuki::shared_ptr<Date> sp4(new Date);
	cout << "sp4.use_count():" << sp4.use_count() << endl; // 1


	sp1->_year++;
	cout << "sp1->_year:" << sp1->_year << endl; // 2027
	cout << "sp2->_year:" << sp2->_year << endl; // 2027
	cout << "sp3->_year:" << sp3->_year << endl; // 2027
	cout << "sp4->_year:" << sp4->_year << endl; // 2026


	/*
	* 赋值构造
	* 1. 对象不存在,拷贝构造
	* 2. 对象存在,赋值构造
	*/

	// 自己给自己赋值
	sp1 = sp2;
	cout << "sp1.use_count():" << sp1.use_count() << endl; // 3


	// 对象不存在,拷贝构造
	yuuki::shared_ptr<Date> sp5 = sp4;
	cout << "sp5.use_count():" << sp5.use_count() << endl; // 2

	// 对象存在,赋值构造
	sp4 = sp1;
	cout << "sp1.use_count():" << sp1.use_count() << endl; // 4

	// 对象存在,赋值构造
	yuuki::shared_ptr<Date> sp6(new Date);
	sp1 = sp6;
	cout << "sp1.use_count():" << sp1.use_count() << endl; // 2

	return 0;
}

shared_ptr 循环引用问题和 weak_ptr 使用原理

  • shared_ptr 大多数情况下管理资源非常合理,支持 RALL ,也支持拷贝。但在循环引用得场景下会导致资源内存泄漏
  • weak_ptr:不支持 RALL ,本质是解决 shared_ptr 的循环引用导致内存泄漏
shared_ptr 循环引用问题

就拿双向链表来解释怎么出现 循环引用 的问题:

  • 我们所知,双向链表两个节点的 next 和 prev 互相指向,如果要析构是两个对象遍历链表析构(用两个对象是防止找不到节点而导致访问野指针)
  1. 前提提醒:整个节点都是由 shared_ptr 构成
  2. 到了 shared_ptr,右边节点由 _next 管理,左边节点由 _prev 管理
  3. 左节点 释放,_next 析构,因 _next 析构,指向的右节点
  4. 右节点 释放,_prve 析构,因 _prve 析构,指向左节点
  • 逻辑上形成闭环,因为谁都不会释放,导致内存泄漏
  • ListNode 结构体中的 _next 和 _prveshared_ptr 改成 weak_ptr,这是就不会增加引用计数,_next 和 _prev 不参与资源释放管理逻辑,就打破了循环应用
weak_ptr(弱引用)
  • 不增加引用计数,用于打破 shared_ptr 的循环引用导致的内存泄漏
  • 不能直接解引用,必须用 lock() 转为 shared_ptr 使用
cpp 复制代码
#include<iostream>
using namespace std;
template<class T>
struct ListNode
{
	T _data;

	/*shared_ptr<ListNode> _next;
	shared_ptr<ListNode> _prev;*/   // 循环引用!a 和 b 永远不会析构

	// 这里改成 weak_ptr,当 n1->_next = n2,绑定 shared_ptr 时
	// 不增加 n2 的引用计数,不参与资源释放的管理,就不会形成循环引用
	weak_ptr<ListNode> _next;  // 弱引用,不增计数
	weak_ptr<ListNode> _prve;


	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	shared_ptr<ListNode<int>> n1(new ListNode<int>);
	shared_ptr<ListNode<int>> n2(new ListNode<int>);

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prve = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	return 0;
}
weak_ptr 应用
  • weak_ptr 不支持 RALL,也不支持访问资源,只支持绑定到 shared_ptr,不增加 shared_ptr 的引用计数,就可以解决上述的循环引用问题
  • expired 关键字:检查指向资源是否过期
  • use_count 关键字:获取 shared_ptr 的引用计数
  • lock 关键字:返回一个管理资源的 shared_ptr,如果资源已经释放,返回一个空对象,资源没被释放,则返回的资源是安全的
cpp 复制代码
#include<iostream>
using namespace std;
#include<string>

int main()
{
	shared_ptr<string> p1(new string("1"));
	shared_ptr<string> p2(p1);
	cout << "*p1 = " << *p1 << endl;
	cout << "*p2 = " << *p2 << endl;

	weak_ptr<string> wp = p1;
	// expired 判断是否释放资源,false 表示没释放, true 表示已释放
	cout << wp.expired() << endl;		// 0:没释放
	cout << wp.use_count() << endl;		// 2

	p1 = make_shared<string>("2");
	cout << "*p1 = " << *p1 << endl;
	cout << wp.expired() << endl;		// 0:没释放
	cout << wp.use_count() << endl;		// 1

	p2 = make_shared<string>("3");
	cout << "*p2 = " << *p2 << endl;

	cout << wp.expired() << endl;		// 1:释放
	cout << wp.use_count() << endl;		// 0

	// p2已被改动,所以计数为1
	wp = p1;
	cout << wp.expired() << endl;		// 0:没释放
	cout << wp.use_count() << endl;		// 1

	shared_ptr<string> p3 = wp.lock();
	cout << wp.expired() << endl;		// 0
	cout << wp.use_count() << endl;		// 2
	// lock 返回一个 shared_ptr,自己生成一个 shared_ptr,保证这份不被制空
	// 可以用 shared_ptr 或 auto 接受类型
	// lock:在资源没释放之前,里面再产生一个 shared_ptr 管理这份资源
	*p3 += "$$$";
	cout << "*p1 = " << *p1 << endl;
	cout << "*p2 = " << *p2 << endl;
	cout << "*p3 = " << *p3 << endl;

	return 0;
}

unique_ptr 与 shared_ptr 删除器使用及模拟实现

删除器 使用(为什么设置删除器)
  • 智能指针析构默认进行 delete 释放资源,如果不是 new 出来的资源,交给智能指针管理析构就会崩溃(如:shared_ptr<Date> sp1(new Date[10]),new[] 了数组,new[] 和 delete 错位了)。为此防止这情况,智能指针支持再构造时给一个删除器(自己定义的可调用对象,其用来释放资源)。因为经常使用 new[],所以C++11把 unique_ptr 和 shared_ptr 都特化了份 [] 的版本,使用时 unique_ptr<Date[]> up1(new Date[5]); shared_ptr<Date[]> sp1(new Date[5]); 就可以方便管理 new[] 的资源
cpp 复制代码
#include<iostream>
#include<memory>
using namespace std;

class Date
{
public:
	Date(int year = 2026, int month = 3, int day = 4)
		:_year(year)
		,_month(month)
		,_day(day)
	{ }

	~Date()
	{
		cout << "~Date()" << endl;
	}

//private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

// 仿函数形式
template<class T>
void DeleteArrayFunc(T* ptr)
{
	delete[] ptr;
}

// 迭代器形式
template<class T>
class DeleteArray
{
public:
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

// 文件形式
class Fclose
{
public:
	void operator()(FILE* ptr)
	{
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
	}
};

int main()
{
	// 错误写法
	//unique_ptr<Date> up1(new Date[5]);
	//shared_ptr<Date> sp1(new Date[5]);

	/* 解决方法1 */
	// 日常经常使用 new[],使用 unique_ptr 和 shared_ptr
	// 实现了哥特化版本,这个版本析构时用 delete[]
	unique_ptr<Date[]> up1(new Date[5]);
	shared_ptr<Date[]> sp1(new Date[5]);

	/* 解决方法2 */
	// 用定值删除器时,unique_ptr 和 shared_ptr 又不一样
	/*
	* unique_ptr 在类声明里传
	* template <class T, class D = default_delete<T>> class unique_ptr;
	* 
	* shared_ptr 在构造函数里面传
	* template <class U, class D> shared_ptr (U* p, D del);
	* 
	*/


	// 仿函数对象做删除器
	unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
	shared_ptr<Date> sp2(new Date[5], DeleteArray<Date>());

	// 函数指针做删除器
	unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);
	shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>); // 仿函数需实例化

	// lambda表达式做删除器
	auto delArrOBJ = [](Date* ptr) {delete[] ptr; };
	unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);
	shared_ptr<Date> sp4(new Date[5], [](Date* ptr) {delete[] ptr; }); // lambda 表达式做删除器

	// 实现其他资源管理器
	shared_ptr<FILE> sp5(fopen("test.cpp", "r"), Fclose());
	shared_ptr<FILE> sp6(fopen("test.cpp", "r"), [](FILE* ptr) {
		delete[] ptr;
		fclose(ptr);
		}); // 在函数参数传,实参传给形参通过模板自动推导 lambda 的类型
			// lambda、函数指针、仿函数都很好用

	unique_ptr<FILE, Fclose> up5(fopen("test.cpp", "r"));
	// 而 unique_ptr 必须传类型,但 lambda 拿不到类型,就很难用 lambda 写 unique_ptr
	// 这时候就可以用 decltype 关键字来推导类型
	auto fcloseFunc = [](FILE* ptr) {fclose(ptr); };
	unique_ptr<FILE, decltype(fcloseFunc)> up6(fopen("test.cpp", "r"), fcloseFunc);

	/*  总结
	* 如果用 unique_ptr 定制删除器,建议用仿函数
	* 如果用 shared_ptr 定制删除器,都可以,lambda 更好
	*/
	
	return 0;
}
删除器 模拟实现
cpp 复制代码
#include<iostream>
#include<memory>
using namespace std;

class Date
{
public:
	Date(int year = 2026, int month = 3, int day = 4)
		:_year(year)
		,_month(month)
		,_day(day)
	{ }

	~Date()
	{
		cout << "~Date()" << endl;
	}

//private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

#include<functional>
namespace yuuki
{
	// 因为类里面访问不了构造函数的模板参数
	// 所以 unique_ptr 实现成 template<class T, class D>
	// 用删除器时必须传类型,用 lambda 时就特别不方便
	template<class T>
	class shared_ptr
	{
	public:
	//-----------------------------------------------------------------------
	// 版本1:支持单参数
		// sp1 默认构造生成 _pcount 计数为 1(有1个模板参数)
		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{ }		// 这里可能会因为没有调用 del 而报错,所以下面写了 lambda 作为缺省值去默认构造 del

		// shared_ptr 删除器写法(有2个模板参数)
		template<class D> // D = delete
		shared_ptr(T* ptr, D del) // 包装器:del 传到默认构造去,D 可能是 lambda、仿函数、函数指针
			:_ptr(ptr)
			,_pcount(new int(1))
			,_del(del) // 难点:需要把删除器保存下来(因为D是构造函数的模板参数,所以定义不了)
		{ }

	// 版本2:支持双参数
		// sp2(sp1) _pcount 计数需++
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
		{
			(*_pcount)++;
		}
	//-----------------------------------------------------------------------


		// shared_ptr 现代写法
		shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
			if (this != &sp) {  // 防止自赋值
				if (_ptr != sp._ptr) {  // 防止指向同一块内存(可选,但好习惯)
					if (--(*_pcount) == 0) {  // 计数减到0才释放
						delete _ptr;
						delete _pcount;
					}
					_ptr = sp._ptr;
					_pcount = sp._pcount;
					++(*_pcount);  // 新增引用计数
				}
			}
			return *this;
		}

		// 每次析构 sp2 _pcount 计数需--
		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				//delete _ptr;
				_del(_ptr);		// 传的是删除器,释放时用删除器释放
				delete _pcount;
			}
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		// 修正 shared_ptr 中 use_count 的声明
		int use_count()
		{
			return (*_pcount);
		}

	private:
		T* _ptr;
		int* _pcount;
		//D _del; // 类里面用不了函数模板的参数

		// 包装器:del 传到默认构造去,D 可能是 lambda、仿函数、函数指针
		function<void(T*)> _del = [](T* ptr) { delete ptr; };
								// 传 lambda 缺省值,lambda 默认是 delete
								// 坑:delete[],会导致程序死循环
	};
}

int main()
{
	yuuki::shared_ptr<Date> sp1(new Date); // 这里会因没有调用到 del 而程序崩溃

	// 定制删除器
	yuuki::shared_ptr<Date> sp2(new Date[10], [](Date* ptr) {delete[] ptr; });

	return 0;
}

make_shared 使用及好处

cpp 复制代码
#include<iostream>
#include<memory>
using namespace std;

class Date
{
public:
	Date(int year = 2026, int month = 3, int day = 4)
		:_year(year)
		,_month(month)
		,_day(day)
	{ }

	~Date()
	{
		cout << "~Date()" << endl;
	}

//private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main()
{
	shared_ptr<Date> sp1(new Date(2026, 3, 4));

	// 先传日期类的参数,new 了个日期类对象交给 make_shared 管理
	// shared_ptr 每一个对象都会有引用计数,每一个引用计数都会向堆申请内存
	// 向堆申请大量的开小块内存,会有性能的消耗,碎片化的内存也会导致内存碎片的问题
	// 这时候就要用到内存池进行管理小块内存

	// make_shared 会在 Date 内存中多开 4bite 存引用计数
	// 这样只用开一次内存,make_shared 会高效一点点
	shared_ptr<Date> sp2 = make_shared<Date>(2026, 3, 4);
	
	return 0;
}

unique_ptr 和 shared_ptr 转为 bool

shared_ptrunique_ptr 都支持 operator bool 的类型转换,如果智能指针对象是一个空对象没有管理资源,则返回 false,否则返回 true,我们可以直接把只能智能指针对象用 if 判断是否为空

cpp 复制代码
#include<iostream>
#include<memory>
using namespace std;

int main()
{
	shared_ptr<Date> sp1(new Date(2026, 3, 4));
	shared_ptr<Date> sp2 = make_shared<Date>(2026, 3, 4);
	shared_ptr<Date> sp4;

	if (sp1)
		cout << "sp1 is not nullptr" << endl;

	if (sp2.operator bool()) // 原始版本为 operator bool
		cout << "sp2 if not nullptr" << endl;

	if (sp4)
		cout << "sp4 is not nullptr" << endl;

	// 报错:禁止使用隐式类型转换 + 拷贝构造
	//shared_ptr<Date> sp5 = new Date(2026, 3, 4);
	shared_ptr<Date> sp6(new Date(2026, 3, 4)); // 正确用法

	return 0;
}

总结

智能指针是现代 C++ 的核心武器:

  • unique_ptr:独占、零开销、noexcept 友好
  • shared_ptr:共享、引用计数
  • weak_ptr:防循环引用

用 make_unique / make_shared 写代码更安全、更高效。

下一站:Linux 命令复习 + 网络编程入门!

代码仓库:https://github.com/yuuki233233/cpp-learning-journey

CSDN 主页:https://blog.csdn.net/yuuki233233

欢迎评论交流,一起卷现代 C++!

相关推荐
小光学长2 小时前
基于ssm的书法学习交流系统25ki07v1(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·开发语言·数据库·学习·ssm
不光头强2 小时前
HashMap知识点
java·开发语言·哈希算法
闻缺陷则喜何志丹2 小时前
【构造 前缀和】P8902 [USACO22DEC] Range Reconstruction S|普及+
c++·算法·前缀和·洛谷·构造
@OuYang2 小时前
android10 应用安装
开发语言·python
_MyFavorite_2 小时前
Python 中通过命令行向函数传参
开发语言·chrome·python
yujunl2 小时前
Net Core8项目不能正常发布
开发语言
lly2024062 小时前
JavaScript Window History
开发语言
老四啊laosi2 小时前
[C++进阶] 16. 继承
c++·继承
jianfeng_zhu2 小时前
用java解决空心金字塔的问题
java·开发语言·python