C++新特性21_shared_ptr与weak_ptr(大量使用,仅看此篇即可;使用方法;注意事项:不能用同一指针去初始化两个shared_ptr;循环引用问题;weak_pt用于解决循环引用问题)

前面介绍了智能指针中早期的两个版本:auto_ptr(已废弃),unique_ptr(不常用,auto_ptr的升级,限制了某些操作避免了一些问题),本篇介绍在开发中真正可能被大量使用的指针shared_ptr。weak_ptr是对shared_ptr的一种补充

1. shared_ptr是带引用计数的智能指针。

1.1 shared_ptr构造

其初始化多了一种写法:std::make_shared

void foo_construct()
{
	int* p = new int(3);//创建int指针

	std::shared_ptr<int> sptr(p);
	std::shared_ptr<int> sptr2(new int(4)); //更建议的初始化方法
	std::shared_ptr<int> sptr3 = sptr2;//拷贝构造 实质是带有引用计数的指针
	std::shared_ptr<int> sptr4 = std::make_shared<int>(5);
}

在这里插入图片描述
这里显然可以看到有引用计数的存在。

通过修改上面例子中的sptr3的作用域,可以发现,出了块作用域之后,shared_ptr对应的引用计数的值减少了。

void foo_construct()
{
		std::shared_ptr<int> sptr2(new int(4));
		{
			std::shared_ptr<int> sptr3 = sptr2;
		}
}

在这里插入图片描述

1.2 shared_ptr注意事项:

1.2.1 如果用同一个指针去初始化两个shared_ptr时,则引用计数仍然会出错:

void foo_test()
{
	int* p = new int(3);
	{
	    //出外层的块作用域又会析构p,这个时候就会重复析构p
		std::shared_ptr<int> sptr(p);
		{
		    //出块作用域就会析构p
			std::shared_ptr<int> sptr2(p); 
		}
	}
}

在这里插入图片描述
参考笔记中记录的过程:
在这里插入图片描述
显然出了最里面的作用域之后,sptr2对象就已经释放了,此时,对于sptr2来说,p的引用计数为0,所有p被释放,但是实际上sptr还存在,所以再释放sptr时,就会0xc0000005。
在这里插入图片描述

因此标准的构造过程,应按照以下方式:

void foo_construct()
{
        //资源的申请只能在构造的时候按照以下方式写
		std::shared_ptr<int> sptr2(new int(4));
		{
		//任何其他的智能指针的初始化只能通过其他智能指针进行
			std::shared_ptr<int> sptr3 = sptr2;
		}
}

1.2.2 为什么需要weak_ptr?:避免循环引用的问题

shared_ptr最大的问题是存在循环引用的问题:两个类同时包含了另一个类的智能指针,互相引用的情况,再进行析构时会出现异常

如果两个类的原始指针的循环使用,那么会出现重复释放的问题:


#include <iostream>
#include <memory>

using namespace std;

class CPerson;
class CSon;

//循环引用的问题
//两个类同时包含了另一个类的智能指针,互相引用
class Cperson
{
public:
	Cperson() {

	}

	//调用方法将外部的智能指针进行赋值
	void Set(std::shared_ptr<CSon> pSon) {
		m_pSon = pSon;
	}

	~Cperson() {
	}

	//包含另外一个儿子类的智能指针
	std::shared_ptr<CSon> m_pSon; 
};

class CSon
{
public:
	CSon() {

	}

	void Set(std::shared_ptr<Cperson> pParent) {
		m_pParent = pParent;
	}

	~CSon() {
	}
	//包含父类的智能指针
	std::shared_ptr<Cperson> m_pParent;
};

void testShared()
{
	CSon* pSon = new CSon();
	Cperson* pPer = new Cperson();

	{
		std::shared_ptr<Cperson> shared_Parent(pPer); //利用父亲的对象定义父亲的智能指针,父类智能指针的引用计数+1
		std::shared_ptr<CSon> shared_Son(pSon); //利用儿子的对象定义儿子的智能指针

		shared_Parent->Set(shared_Son); //父亲类对象调用儿子的智能指针方法
		shared_Son->Set(shared_Parent); //调用的方法中shared_Parent赋值给子类中的m_pParent,父类的引用计数+1,此处自己感觉应该是pSon->(0607),下篇是原理解析可以印证

		//打印出使用次数
		printf("pSon : use_count = %d\r\n", shared_Son.use_count());
		printf("pPer : use_count = %d\r\n", shared_Parent.use_count());
	}


}

int main(int argc, char* argv[])
{
	testShared();

	return 0;
}

出了作用域,按道理应该释放,但是此时对象并未析构
在这里插入图片描述
在这里插入图片描述
最后两者的引用计数均为1,原因是出了块作用域之后,两个shared_parent和shared_son均会析构,在这两个智能指针的内部,均会先去判断对应的内部指针-1是否为0,显然这里相互引用的情况下,引用计数初值为2,减1后值为1,所以两个指针均不会被释放。

这里,其实只需要一个释放了,另外一个也能跟着释放,可以采用弱指针,即人为的迫使其中一个引用计数为1,从而打破闭环。

这里只需要将上例子中的任意一个强指针改为弱指针即可。
在这里插入图片描述
此时,两个内部指针均会得到释放。
在这里插入图片描述
原因是,弱指针的引用不会增加原来的引用计数,那么就使得引用不再是闭环,所以在出作用域之后,全部得到释放。

2. weak_ptr的使用

  • weak_ptr本身并不具有普通内部指针的功能,而只是用来观察其对应的强指针的使用次数。

  • 因此,这里弱指针的在使用上,实际上是一个特例,即不增加引用计数也能获取对象,因此,实际上在使用弱指针时,不能通过弱指针,直接访问内部指针的数据,而应该是先判断该弱指针所观察的强指针是否存在(调用expired()函数),如果存在,那么则使用lock()函数来获取一个新的shared_ptr来使用对应的内部指针。

  • 实际上,如果不存在循环引用,就不需要使用weak_ptr了,这种做法仍然增加了程序员的负担,所以不如java c#等语言垃圾回收机制省心。

3. 学习视频地址:shared_ptr与weak_ptr

4.学习笔记:shared_ptr与weak_ptr笔记

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

十月旧城

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

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

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

打赏作者

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

抵扣说明:

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

余额充值