【C++】智能指针


前言

在上一篇异常的文章中我们提及到了利用RAII思想去解决内存泄露问题,而智能指针就是RAII思想的产物,智能指针到底智能在什么地方呢?下面我们一起来学习吧!!

一、为什么需要智能指针

为什么需要智能指针呢?我们来回顾一下异常章节出现的场景:

#include <iostream>
using namespace std;
int div()
{
   
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
   
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int;
	int* p2 = nullptr;
	cout << div() << endl;
	delete p1;
	delete p2;
}

int main()
{
   
	try
	{
   
		Func();
	}
	catch (exception& e)   // C++库中提供的可以接收任意类型的基类
	{
   
		cout << e.what() << endl;
	}

	return 0;
}

上述三个问题该如何解决?我们利用重新抛出就能解决上述的问题,那么该如何来写呢?

/*
思路:
	如果p1未申请成功,抛出异常直接被主函数捕获返回主函数;
	如果p1申请成功,而p2未申请成功,则需在异常被主函数捕获前delete p1,再重新抛出异常交给主函数处理。
	此时p1与p2都申请成功,div抛出异常则需在异常被主函数捕获前delete p1 p2,再重新抛出异常交给主函数处理。
*/
#include <iostream>
using namespace std;

int div()
{
   
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}

void Func()
{
   
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int;
	int* p2 = nullptr;
	try
	{
   
		p2 = new int;
	}
	catch (...)
	{
   
		delete p1;
		throw;
	}
	
	try
	{
   
		cout << div() << endl;
	}
	catch (...)
	{
   
		delete p1;
		delete p2;
		throw;
	}
	
	delete p1;
	delete p2;
}

int main()
{
   
	try
	{
   
		Func();
	}
	catch (exception& e)   // C++库中提供的对任意类型接收的基类
	{
   
		cout << e.what() << endl;
	}

	return 0;
}

我们可以看到问题虽然是解决了,但是设计出的代码确是如此的丑陋,那么对于内存申请与释放问题能不能交给编译器去处理呢?这样就不用我们时时刻刻去操心内存泄露的问题了——智能指针!!

二、智能指针的使用和原理

2.1 RAII思想

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象

这种做法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效

接下来我们基于RAII思想来简单设计一个SmartPtr类,实现对象来管理资源:

template<class T>
class SmartPtr
{
   
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{
   }

	~SmartPtr()
	{
   
		if (_ptr)
		{
   
			cout << "delete:" << _ptr << endl;
			delete _ptr;
		}
	}
private:
	T* _ptr;
};

int div()
{
   
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}

void Func()
{
   
	// div抛异常, 主函数会捕获到异常, 此时sp1与sp2对象出了Func函数作用域会自动调用析构函数完成资源的清理工作
	SmartPtr<int> sp1(new int(1));
	SmartPtr<int> sp2(new int(2));

	cout << div() << endl;
}

int main()
{
   
	try
	{
   
		Func();
	}
	catch (exception& e)
	{
   
		cout << e.what() << endl;
	}

	return 0;
}

在这里插入图片描述

2.2 智能指针的原理

上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:SmartPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr 
{
   
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{
   }

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

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

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

private:
	T* _ptr;
};

智能指针的原理总结: 利用 RAII 思想设计释放资源的类,该类重载了*->,其具有像指针一样的行为。

如果我们想把这个类的功能再丰富一下,比如说让其可以拷贝构造、赋值重载,我们来看看下面的情况:
在这里插入图片描述

我们未重写拷贝构造,那么默认拷贝构造就是浅拷贝,两个对象的成员都指向了同一份资源,那么析构时对同一块空间delete两次就会出现不确定的行为此时程序崩溃,那么按照我们之前所说的浅拷贝不行就换深拷贝分别指向不同块,然后将数据拷贝过来吗?显然在智能指针这里深拷贝是不行的,因为智能指针就需要浅拷贝,使两个对象的成员指向同一份资源!!智能指针拷贝见下图:

在这里插入图片描述

那么如何才能实现智能指针的浅拷贝?使得在析构时不对同一份资源析构两次呢?早期C++库中就提供了几种智能指针的设计方案,下面我们一起来看看它们的设计原理。

2.3 std::auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。auto_ptr的实现原理:管理权转移的思想。

下面我们根据库中的设计简化模拟实现了一份curry::auto_ptr来了解它的原理:

namespace curry
{
   
	template<class T>
	class auto_ptr
	{
   
	public:
		auto_ptr(T* ptr 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

malloc不出对象

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值