C++——智能指针

智能指针

布隆过滤器 (1)

内存泄漏

  1. 什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内 存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对 该段内存的控制,因而造成了内存的浪费。

  2. 内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现 内存泄漏会导致响应越来越慢,最终卡死。

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一 块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分 内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

  • 系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放 掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

内存泄漏很常见,解决方案通常分为两种:

  1. 事前预防型。如智能指针等。
  2. 事后查错型。如泄 漏检测工具。

智能指针解决内存泄漏问题

场景:main函数里调用func函数,在func函数里申请了一个int大小的空间,然后调用div函数,在div函数进行除法操作,若出现除零,则直接抛异常,直接跳转到main函数捕获异常,从而没有释放掉在func函数内申请的资源,即内存泄漏。

#include<iostream>
#include<vector>
using namespace std;

int div()
{
   
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
   
	int* ptr = new int;//new了一个int大小的资源
	cout << div() << endl;
	delete ptr;//因为抛异常导致这个int资源没有被正常释放
    cout << "delete ptr" << endl;
}
int main()
{
   
	try
	{
   
		func();
	}
	catch (exception& e)
	{
   
		cout << e.what() << endl;
	}
	return 0;
}

解决方法一:在func函数内捕获一次异常,进行对申请资源的释放后,再将异常抛出,让外层栈帧去捕获解决异常。

int div()
{
   
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
   	int* ptr = new int;//new了一个int大小的资源
try {
   
	cout << div() << endl;
}
catch (...)
{
   

	delete ptr;//因为抛异常导致这个int资源没有被正常释放
	cout << "delete ptr" << endl;
	throw;
}

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

image-20230903191348146

解决方案二:让智能指针对资源进行管理,调用智能指针的构造函数申请资源,调用智能指针的析构函数释放资源。

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;

};

int div()
{
   
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
   	
	smartptr<int> sma(new int);
	cout << div() << endl;	
}
int main()
{
   
	try
	{
   
		func();
	}
	catch (exception& e)
	{
   
		cout << e.what() << endl;
	}
	return 0;
}

image-20230903192627650

  • 在构造sma对象时,让smartptr的构造函数去申请资源。
  • 当退出func函数这层栈帧时,sma对象的生命周期结束,自动调用smartptr的析构函数去释放资源。
  • 将申请到的资源交给一个smartptr对象进行管理,这样无论是否出现除零都能正常释放资源。
  • 此外为了让smartptr对象能够像原生指针一样使用,还需要对*->运算符进行重载。

智能指针的使用及原理

RAII

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

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

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

需要注意的是:

  1. 在对象构造时获取资源,在对象析构时释放资源,即要具备RAII特性。
  2. *->运算符进行重载,使得对象具有像指针一样的行为。
  3. 智能指针对象的拷贝问题。
智能指针对象的拷贝问题
int main()
{
   
	smartptr<int> sm1(new int);
	smartptr<int> sm2(sm1);//拷贝构造

	smartptr<int> sm3(new int);//赋值重载
	sm3 = sm1;
    return 0;
}

image-20230903194741686

  • 编译器默认生成的拷贝构造是对内置类型完成值拷贝(浅拷贝),因此sm2对象对sm1对象的拷贝构造,是对sm1对象的资源的地址拷贝过来,即sm1和sm2对同一份资源进行管理,当退出栈帧对象的生命周期结束时,sm1和sm2一起会对一份资源析构两次,造成越界问题。
  • 编译器默认生产的拷贝赋值函数是对内置类型完成值拷贝(浅拷贝)。因此将sm1对象赋值給sm3对象,是将sm1管理资源的地址赋值給sm3的_ptr,即sm1和sm3对同一份资源进行管理,当退出栈帧对象的生命周期结束时,sm1和sm3一起会对同一份资源析构两次,造成越界问题。

智能指针要模拟出原生指针的行为,而我们将一个指针赋值給另一个指针,其目的就是让两个指针对同一份资源进行管理,但单纯的浅拷贝会导致空间多次释放,因此根据实现的场景不同,衍生出不同的智能指针。

C++中的智能指针

auto_ptr

auto_ptr的实现目的:对资源的管理权进行转移

auto_ptr是C++98中引入的智能指针,auto_ptr通过管理权转移的方式解决智能指针的拷贝问题,保证一个资源在任何时刻都只有一个对象在对其进行管理,这时同一个资源就不会被多次释放了。

auto_ptr的模拟实现

template<class T>
class Auto_ptr
{
   
public:
	Auto_ptr(T* ptr=nullptr):_ptr(ptr){
   }
	~Auto_ptr() {
    cout << "delete _ptr" << endl; delete _ptr; }

	T* operator->()
	
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值