c++智能指针

1.智能指针的概念

智能指针就像一个指针的管理者可以管理指针的生死;

2.出现的原因

在以下代码中先new了指针;但是div函数中抛出了异常;使得执行段跳到接受的位置,使得new指针的创立的内存段无法删除,造成内存泄漏;

int Div(int a, int b)
{
	if (b == 0)
		throw "除0错误";
	else
		return a / b;
}
 
void Func()
{
	int* p1 = new int[5];
	int* p2 = new int[5];
 
	//这里Div函数会抛异常,main函数会捕获异常,delete[]没有执行,引发内存泄漏
	int ret = Div(5, 0);
 
	delete[] p1;
	delete[] p2;
}
 
int main()
{
	try
	{
		Func();
	}
	catch (std::exception& e)
	{
		std::cout << e.what() << std::endl;
	}
	catch (...)
	{
		std::cout << "未知错误" << std::endl;
	}
 
	return 0;
}

采用智能指针管理资源,有如下优点:
将资源管理的责任转移给智能指针对象,不用显示地释放资源,杜绝了异常安全问题。
保证对象管理的资源在其生命周期内有效。

3.智能指针的原理

智能指针,顾名思义,不能仅仅是用资源管理,还应当具有管理指针的一般功能。因此,需要重载operator*,operator->函数,用于访问指针指向的资源;

智能指针原理总结:

RAII,管理资源,对象创建时申请资源,对象生命周期结束时释放资源。 具有像指针一样的行为:operator*、operator->。

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
    {}
	~SmartPtr()
	{
		delete[] _ptr;
		cout << "delete:" << _ptr << endl;
	}
	T* get()
	{
		return _ptr;
	}
	T&operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	T& operator[](size_t i)
	{
		return _ptr[i];
	}

private:
	T* _ptr;
};

4.智能指针的拷贝问题

我们要求智能指针具有一般的指针行为,因此,我们也就需要智能指针支持拷贝。但是,智能指针中的成员涉及到执行动态申请资源的指针,按照一般要求,应当进行深拷贝。

但是如果我们进行深拷贝,就会让两个智能指针指向不同的空间,但是我们所希望的是两个指针共同管理一块资源,因此我们就是要浅拷贝(值拷贝)。

但是值拷贝会存在对同一块空间多次释放的问题,对此,C++标准库中的智能指针auto_ptr和shared_ptr分别采用了管理权转移和引用计数的方法来解决问题,但一般会通过引用计数解决多次释放的问题
在这里插入图片描述

5.c++拷贝问题的解决方法

5.1 auto_ptr(自动指针)(c++98不好用)

5.1.1auto_ptr的原理

auto_ptr采用管理权转移的方法进行赋值和拷贝构造,假设原先有一个auto_ptr对象p1,要通过p1构造p2,当拷贝构造完成后,用于拷贝构造传参的对象p1中管理资源的指针会被更改为nullptr,赋值也一样,假设p2=p1,p1中资源的管理权会转移给p2,p2原本的资源会被释放。

采用管理权转移的方法进行智能指针拷贝是一种极不负责任的行为,auto_ptr已经被很多公司明令禁止使用,一般项目中也极少使用auto_ptr。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里auto_ptr指针纯在问题:对于成就p2后p1就无法进行访问了;这和正常指针一定一定都不一样;
所以这个指针就可以忽略c++98的垃圾

5.2 auto_ptr的模拟实现

这里最需要注意的问题是实现operator=函数时的自赋值检验,因为p2=p1要涉及到资源释放,如果p1和p2指向同一块资源,p2的资源被先行释放,那么p2=p1后,p2依旧指向原来的空间,但这块空间的使用权利已经换给了操作系统。

template<class T>
class auto_ptr
{
public:
	//构造函数
	auto_ptr(T* ptr == nullptr)
		:_ptr(ptr)
	{}
	//拷贝构造函数
	auto_ptr(auto_ptr<T>& ap)
		:_ptr(ap._ptr)
	{
		ap._ptr = nullptr;
	}
	//赋值函数
	auto_ptr<T>& operator=(auto_ptr<T>& ap)
	{
		if (_ptr != ap._ptr)//注意这里不可以自己
		{
			delete _ptr;//这里自己内容需要删除;
			_ptr = ap._ptr;
			ap._ptr = nullptr;
		}
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}

	~auto_ptr()
	{
		delete _ptr;
	}
private:
	T* _ptr;
};

5.2 unique_ptr(直接将拷贝构造和赋值删除)(c++11)

unique直接将拷贝构造和赋值禁止,也就不存在浅拷贝的多次释放同一块空间的问题。

namespace zhang
{
	template<class T>
	class unique_ptr
	{
	public:
		//构造函数
		unique_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		{ }
 
		//使用delete关键字强行禁止拷贝构造函数和赋值函数的生成
		unique_ptr(const unique_ptr<T>& up) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
 
		T& operator*()
		{
			return *_ptr;
		}
 
		T* operator->()
		{
			return _ptr;
		}
 
		//析构函数
		~unique_ptr()
		{
			delete _ptr;
		}
 
	private:
		T* _ptr;
	};
}

5.3 shared_ptr

shared是C++11标准库中新纳入的智能指针,它通过引用计数的方法,较好的解决了拷贝和赋值的问题,是实际项目中最常用的智能指针。

5.3.1 shared_ptr的常用接口

在这里插入图片描述

5.3.2 shared_ptr的拷贝构造和赋值问题

shared内部有一个成员变量long int* _pcount,它指向一块存储引用计数的空间,当进行拷贝构造时,引用计数+1,即:++(_pcount)。
进行赋值操作(sp2 = sp1)时,首先应当检查自赋值,如果是自赋值直接返回
this即可。如果不是自赋值,那么首先将sp2的引用计数-1,如果sp2的引用计数-1后变为了0,那么就释放sp2的资源,然后赋予sp2管理sp1管理的资源的权限,sp2和sp1共用一个引用计数,引用计数+1。
调用析构函数时,先让引用计数-1,如果此时引用计数变为0,就释放资源。
在这里插入图片描述

这里的计数使用使用使用类内的静态变量可以吗?
不对,对于同一类型的不同变量的创建也会改变原来变量的指针数;
那么可以使用来存储呢
使用指针来代那片空间;再在拷贝构造和赋值函数进行配合;实现对于用一个变量存在一个计数变量;

5.3.3 shared_ptr的模拟实现
template<class T>
class shared_ptr
{
public:
	//构造函数
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new long int(1))
	{}
	//拷贝构造函数
	shared_ptr(shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _pcount(sp._pcount)
	{
		++(*_pcount);
	}
	//赋值函数
	shareed_ptr<T>& operator=(shared_ptr<T>& sp)
	{
		if (_ptr == sp._ptr)
		{
			return *this;
		}
		//this的引用计数-1,并判断是否需要释放资源
		if--* _pcount) == 0)
		{
		delete _ptr;
		delete _pcount;
         }
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		++(*_pcount);
		return *this;

	}
	int use_count()
	{
		return *_pcount;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	bool unique()//查看是否为重复;
	{
		retrun* _pcount == 1;
	}
	//析构函数
	~shared_ptr()
	{
		if (--(*_pcount) == 0)
		{
			delete _ptr;
			delete _pcount;
		}
	}
private:
	T* _ptr;//指向动态申请的指针
	long int*_pcount//引用计数
};
5.3.4 shared_ptr的循环引用问题

在绝大部分情况下,shared_ptr能够解决智能指针赋值造成的多次析构问题,也不会引发内存泄漏。但是,代码4.1展现了一种特殊情况,定义一个Node节点,其中包含两个shared_ptr成员_prev和_next。在主函数中实例化出两个shared_ptr对象n1和n2,n1的_next指向n2,n2的_prev指向n1,n1和n2相互指向对方,这样就属于循环引用,会造成n1和n2的资源释放失败,引发内存泄漏问题。

5.3 weak_ptr

weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。

1.弱指针的使用;
weak_ptr wpGirl_1; // 定义空的弱指针
weak_ptr wpGirl_2(spGirl); // 使用共享指针构造
wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针

2.弱指针也可以获得引用计数;
wpGirl_1.use_count()

3.弱指针不支持 * 和 -> 对指针的访问;
在这里插入图片描述
4.在必要的使用可以转换成共享指针 lock();

shared_ptr<Girl> sp_girl;
sp_girl = wpGirl_1.lock();

// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;
shared_ptr<Boy> spBoy(new Boy());
shared_ptr<Girl> spGirl(new Girl());

// 弱指针的使用
weak_ptr<Girl> wpGirl_1;			// 定义空的弱指针
weak_ptr<Girl> wpGirl_2(spGirl);	// 使用共享指针构造
wpGirl_1 = spGirl;					// 允许共享指针赋值给弱指针

cout << "spGirl \t use_count = " << spGirl.use_count() << endl;
cout << "wpGirl_1 \t use_count = " << wpGirl_1.use_count() << endl;

	
// 弱指针不支持 * 和 -> 对指针的访问
/*wpGirl_1->setBoyFriend(spBoy);
(*wpGirl_1).setBoyFriend(spBoy);*/

// 在必要的使用可以转换成共享指针
shared_ptr<Girl> sp_girl;
sp_girl = wpGirl_1.lock();

cout << sp_girl.use_count() << endl;
// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;


                     

当然这只是一些使用上的小例子,具体用法如下:

请看Boy类

#include <iostream>
#include <string>
#include <memory>

using namespace std;

class Girl;

class Boy {
public:
	Boy() {
		cout << "Boy 构造函数" << endl;
	}

	~Boy() {
		cout << "~Boy 析构函数" << endl;
	}

	void setGirlFriend(shared_ptr<Girl> _girlFriend) {
		this->girlFriend = _girlFriend;


		// 在必要的使用可以转换成共享指针
		shared_ptr<Girl> sp_girl;
		sp_girl = this->girlFriend.lock();

		cout << sp_girl.use_count() << endl;
		// 使用完之后,再将共享指针置NULL即可
		sp_girl = NULL;
	}

private:
	weak_ptr<Girl> girlFriend;
};

class Girl {
public:
	Girl() {
		cout << "Girl 构造函数" << endl;
	}

	~Girl() {
		cout << "~Girl 析构函数" << endl;
	}

	void setBoyFriend(shared_ptr<Boy> _boyFriend) {
		this->boyFriend = _boyFriend;
	}

private:
	shared_ptr<Boy> boyFriend;
};


void useTrap() {
	shared_ptr<Boy> spBoy(new Boy());
	shared_ptr<Girl> spGirl(new Girl());

	spBoy->setGirlFriend(spGirl);
	spGirl->setBoyFriend(spBoy);
}


int main(void) {
	useTrap();

	system("pause");
	return 0;
}

在这里插入图片描述

在类中使用弱指针接管共享指针,在需要使用时就转换成共享指针去使用即可!
自此问题完美解决!

expired函数的用法

现在加上weak_ptr指针的expired函数的用法!

expired:判断当前weak_ptr智能指针是否还有托管的对象,有则返回false,无则返回true

如果返回true,等价于 use_count() == 0,即已经没有托管的对象了;当然,可能还有析构函数进行释放内存,但此对象的析构已经临近(或可能已发生)。

示例
演示如何用 expired 检查指针的合法性。
在网上找了一段代码,加上自己的注释理解

#include <iostream>
#include <memory>

std::weak_ptr<int> gw;

void f() {

	// expired:判断当前智能指针是否还有托管的对象,有则返回false,无则返回true
	if (!gw.expired()) {
		std::cout << "gw is valid\n";	// 有效的,还有托管的指针
	} else {
		std::cout << "gw is expired\n";	// 过期的,没有托管的指针
	}
}

int main() {
	{
		auto sp = std::make_shared<int>(42);
		gw = sp;

		f();
	}

	// 当{ }体中的指针生命周期结束后,再来判断其是否还有托管的指针
	f();

	return 0;
}

在这里插入图片描述
在 { } 中,sp的生命周期还在,gw还在托管着make_shared赋值的指针(sp),所以调用f()函数时打印"gw is valid\n";
当执行完 { } 后,sp的生命周期已经结束,已经调用析构函数释放make_shared指针内存(sp),gw已经没有在托管任何指针了,调用expired()函数返回true,所以打印"gw is expired\n";

7.总结

智能指针虽然使用起来很方便,但是要注意使用智能指针的一些陷阱,否则会造成严重的内存报错或者内存泄露等问题!

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值