【C++】智能指针介绍


🎆个人主页:夜晚中的人海

今日语录:当你在荒废时间,有多少人在拼命。别在最该奋斗的日子,选择了安逸。

文章目录

🚀一、RAII和智能指针

RAII是Resource Acquisition Is Initialization的缩写,它的意思是获取资源立即初始化。本质是⼀种利用对象生命周期来管理获取到的动态资源,避免资源泄漏

智能指针类除了满足RAII的设计思路,还要方便对资源进行访问,所以智能指针类还会想迭代器类⼀样,重载 operator*/operator->/operator[]等运算符,

例:

cpp 复制代码
template<class T>
class smart_ptr
{
public:
	smart_ptr(T* ptr)
		:_ptr(ptr)
	{}

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

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

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

	T& operator[](size_t i)
	{
		return _ptr[i];
	}
private:
	T* _ptr;
};

🎉二、C++标准库智能指针的使用

C++标准库中的智能指针都在< memory >这个头文件 下面,因此在使用前需要包含这一头文件。除了weak_ptr以外,其它都符合RAII和像指针⼀样访问的行为,原因主要是解决智能指针拷贝时的思路不同

C++标准库主要提供了四种智能指针:
auto_ptr: 它是在C++98中设计出来的智能指针,其特点是拷贝时把被拷贝对象的资源管理权转移给拷贝对象 ,这个设计是非常糟糕的,因为它会让被拷贝的对象悬空,当我们对其进行访问时会发生报错。因此在日常工作中不建议使用这一指针

以下这三种指针都是在C++11中设计出来的
unique_ptr:它的意思是唯一指针,其特点是不支持拷贝只支持移动,因此在不需要拷贝的场景下就可以使用它

shared_ptr: 它的意思是共享指针,其特点是支持拷贝也支持移动,因此在需要拷贝的场景下就可以使用它

weak_ptr:它的意思的弱指针,其完全不同于上面的智能指针,因为它不支持RAII ,也就意味着不能用它直接管理资源。其作用是用来解决循环引用的问题

例:

cpp 复制代码
struct Date
{
	int _year;
	int _month;
	int _day;
	Date(int year = 1,int month = 1,int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

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

int main()
{
	auto_ptr<Date> ap1(new Date);
	//ap1的管理权转移给ap2
	auto_ptr<Date> ap2(ap1);
	//报错:对空指针进行访问
	ap1->_year++;

	unique_ptr<Date> up1(new Date);
	//报错:不支持拷贝
	unique_ptr<Date> up2(up1);
	//支持移动,移动后up1也为空,不能对其进行访问
	unique_ptr<Date> up2(move(up1));

	shared_ptr<Date> sp1(new Date);
	//支持拷贝也支持移动
	shared_ptr<Date> sp2(sp1);
	shared_ptr<Date> sp3(move(sp1));

	return 0;
}

删除器

智能指针析构时默认是进行delete释放资源 ,这也就意味着如果不是new出来的资源交给智能指针管理,析构时就会崩溃。因此智能指针支持在构造时给⼀个删除器 ,而删除器本质就是⼀个可调用对象,这个可调用对象中实现你想要的释放资源的方式。即当我们在构造智能指针时,给了定制的删除器,智能指针析构时就会调用我们设计出的删除器去释放资源

cpp 复制代码
struct Date
{
	int _year;
	int _month;
	int _day;
	Date(int year = 1,int month = 1,int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

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

template<class T>
class DeleteArray
{
public:
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

template<class T>
void DeleteArrayFunc(T* ptr)
{
	deletr[] ptr;
};

int main()
{
	//这样去构造程序会报错
	//unique_ptr<Date> up(new Date[10]);
	//shared_ptr<Date> sp(new Date[10]);

	//解决方案1:因为new[]经常使用,因此unique_ptr和shared_ptr 
    //实现了⼀个特化版本,这个特化版本在析构时用的delete[] 
	unique_ptr<Date[]> up(new Date[10]);
	shared_ptr<Date[]> sp(new Date[10]);

	//解决方案2:使用仿函数对象做删除器
	//这里需要注意:unique_ptr和shared_ptr在支持删除器的方式是有所不同的
	//unique_ptr在类模板参数支持的,shared_ptr是构造函数参数支持的 
	unique_ptr<Date, DeleteArray<Date>> up1(new Date[10]);
	shared_ptr<Date> sp1(new Date[10], DeleteArray<Date>());

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

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

	return 0;
}

使用时需要注意以下几点:

1.shared_ptr 除了支持用指向资源的指针构造,还支持 make_shared 用初始化资源对象的值直接构造。

2.shared_ptr 和 unique_ptr 都支持了operator bool的类型转换,如果智能指针对象是⼀个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。

3.shared_ptr 和 unique_ptr 在构造函数中都得使用explicit来修饰,防止普通指针隐式类型转换成智能指针对象。

例:

cpp 复制代码
int main()
{
	shared_ptr<Date> sp1(new Date(2025, 10, 9));
	shared_ptr<Date> sp2 = make_shared<Date>(2025, 10, 9);

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

	return 0;
}

🚘三、智能指针的原理及其模拟实现

1.auto_ptr

cpp 复制代码
template<class T>
class auto_ptr
{
public:
	auto_ptr(T* ptr)
		:_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)
		{
			if (_ptr)
				delete _ptr;
			_ptr = ap._ptr;
			ap._ptr = nullptr;
		}
		return *this;
	}

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

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

2.unique_ptr

cpp 复制代码
	template<class T>
	class unique_ptr
	{
	public:
		//	加explicit是防止普通指针隐式类型
		// 	转化为智能指针对象
		explicit unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

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

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

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

		//不支持左值的拷贝构造
		unique_ptr(const unique_ptr<T>& up) = delete;
		//不支持左值的赋值
		unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
		//支持右值的拷贝构造
		unique_ptr(unique_ptr<T>&& up)
			:_ptr(up._ptr)
		{
			up._ptr = nullptr;
		}
		//支持右值的赋值
		unique_ptr<T>& operator=(unique_ptr<T>&& up)
		{
			delete _ptr;
			_ptr = up._ptr;
			up._ptr = nullptr;
		}
	private:
		T* _ptr;
	};
}

3.重点:shared_ptr

shared_ptr的拷贝底层是采用引用计数的方式来实现的,让多个shared_ptr对象共用同一份资源

cpp 复制代码
	template<class T>
	class shared_ptr
	{
	public:
		explicit shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
		{}

		//拷贝
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
		{
			++(*_pcount);
		}

		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}
		}

		//赋值
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				if (--(*_pcount) == 0)
				{
					delete _pcount;
					delete _ptr;
				}
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);
			}
			return *this;
		}
		

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

		T& operator*()
		{
			return *_ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
	};

定制删除器版本:

cpp 复制代码
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)
		,_del(del)
		,_pcount(new int(1))
	{}

	void release()
	{
		if (--(*_pcount) == 0)
		{
			_del(_ptr);
			delete _pcount;
			_ptr = nullptr;
			_pcount = nullptr;
		}
	}

	shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		,_pcount(sp._pcount)
		,_del(sp._del)
	{
		++(*_pcount);
	}

	~shared_ptr()
	{
		release();
	}

	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			release();
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			++(*_pcount);
			_del = sp._del;
		}
		return *this;
	}

	int use_count() const
	{
		return *_pcount;
	}

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

	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pcount;
	function<void(T*)> _del = [](T* ptr) {delete ptr; };
};

🎡四、shared_ptr和weak_ptr之间的关系

1.shared_ptr循环引用的问题

shared_ptr在大多数情况下管理资源都非常合适,但在循环引用场景下会出现内存泄漏的问题,因此我们要学会使用weak_ptr来解决这类问题

当n1和n2进行析构时,引用计数为1不为0,导致内存泄漏

2.weak_ptr的介绍及其模拟实现

weak_ptr不支持RAII,也不支持访问资源,它支持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引用计数,因此就可以解决循环引用的问题

• weak_ptr也没有重载operator*和operator->等,因为它不参与资源管理,但如果它绑定的shared_ptr已经释放了资源,我们在对其进行访问,那是非常危险的。因此它设计出expired帮助我们检查指向的资源是否过期,使用use_count也可获取shared_ptr中的引用计数

例:

🏝️五、C++11和boost智能指针中的关系

Boost库是为C++语言标准库提供扩展的⼀些C++程序库的总称,C++11及之后的新语法和库有很多都是从Boost中来的

C++98中产生了第⼀个智能指针auto_ptr

C++boost库也给出了更实用的scoped_ptr/scoped_array和shared_ptr/shared_array和weak_ptr等

C++TR1,引入了shared_ptr等,不过注意的是TR1并不是标准版

C++11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的

⭐六、内存泄漏

1.什么是内存泄漏以及造成的危害

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存,⼀般是忘记释放或者发生异常释放程序未能执行导致的。

危害 :普通程序在资源少的情况下运行⼀会就结束了出现内存泄漏问题也不是很大。但在长期运行的程序以及有大量资源的情况下出现内存泄漏,影响是非常大的,如操作系统、后台服务、长时间运行的客户端等等,不断出现内存泄漏会导致可用内存不断变少,各种功能响应越来越慢,最终卡死。

2.如何避免内存泄漏

1.养成良好的编码规范,申请的内存空间记着匹配的去释放

2.尽量使用智能指针来管理资源,如果在某些场景比较特殊的情况下,采用RAII思想自己造个轮子来管理

3.定期使用内存泄露工具

今天的分享就到这里了,感谢您的评阅,我们下期再见!

相关推荐
用户2018792831672 小时前
后台Activity输入分发超时ANR分析(无焦点窗口)
android
正在走向自律3 小时前
RSA加密从原理到实践:Java后端与Vue前端全栈案例解析
java·前端·vue.js·密钥管理·rsa加密·密钥对·aes+rsa
咯哦哦哦哦3 小时前
关于QT 打印中文 乱码问题
java·数据库·qt
爱读源码的大都督3 小时前
天下苦@NonNull久矣,JSpecify总算来了,Spring 7率先支持!
java·后端·架构
chennn123 小时前
c++相关学习
开发语言·c++·学习
木头没有瓜3 小时前
Slf4j 接口文档左侧菜单有显示,但是点击后空白
java
野犬寒鸦3 小时前
从零起步学习Redis || 第十二章:Redis Cluster集群如何解决Redis单机模式的性能瓶颈及高可用分布式部署方案详解
java·数据库·redis·后端·缓存
游戏开发爱好者83 小时前
BShare HTTPS 集成与排查实战,从 SDK 接入到 iOS 真机调试(bshare https、签名、回调、抓包)
android·ios·小程序·https·uni-app·iphone·webview
m0_552200824 小时前
《UE5_C++多人TPS完整教程》学习笔记61 ——《P62 武器开火特效(Fire Weapon Effects)》
c++·游戏·ue5