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

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

C++新特性21_shared_ptr与weak_ptr

  • 1. shared_ptr是带引用计数的智能指针。
    • 1.1 shared_ptr构造
    • 1.2 shared_ptr注意事项:
      • 1.2.1 如果用同一个指针去初始化两个shared_ptr时,则引用计数仍然会出错:
      • 1.2.2 为什么需要weak_ptr?:避免循环引用的问题
  • 2. weak_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);
}

C++新特性21_shared_ptr与weak_ptr(大量使用,仅看此篇即可;使用方法;注意事项:不能用同一指针去初始化两个shared_ptr;循环引用问题;weak_pt用于解决循环引用问题)_第1张图片
这里显然可以看到有引用计数的存在。

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

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

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

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); 
		}
	}
}

C++新特性21_shared_ptr与weak_ptr(大量使用,仅看此篇即可;使用方法;注意事项:不能用同一指针去初始化两个shared_ptr;循环引用问题;weak_pt用于解决循环引用问题)_第3张图片
参考笔记中记录的过程:
C++新特性21_shared_ptr与weak_ptr(大量使用,仅看此篇即可;使用方法;注意事项:不能用同一指针去初始化两个shared_ptr;循环引用问题;weak_pt用于解决循环引用问题)_第4张图片
显然出了最里面的作用域之后,sptr2对象就已经释放了,此时,对于sptr2来说,p的引用计数为0,所有p被释放,但是实际上sptr还存在,所以再释放sptr时,就会0xc0000005。
C++新特性21_shared_ptr与weak_ptr(大量使用,仅看此篇即可;使用方法;注意事项:不能用同一指针去初始化两个shared_ptr;循环引用问题;weak_pt用于解决循环引用问题)_第5张图片

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

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 
#include 

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;
}

出了作用域,按道理应该释放,但是此时对象并未析构
C++新特性21_shared_ptr与weak_ptr(大量使用,仅看此篇即可;使用方法;注意事项:不能用同一指针去初始化两个shared_ptr;循环引用问题;weak_pt用于解决循环引用问题)_第6张图片
C++新特性21_shared_ptr与weak_ptr(大量使用,仅看此篇即可;使用方法;注意事项:不能用同一指针去初始化两个shared_ptr;循环引用问题;weak_pt用于解决循环引用问题)_第7张图片
最后两者的引用计数均为1,原因是出了块作用域之后,两个shared_parent和shared_son均会析构,在这两个智能指针的内部,均会先去判断对应的内部指针-1是否为0,显然这里相互引用的情况下,引用计数初值为2,减1后值为1,所以两个指针均不会被释放。

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

这里只需要将上例子中的任意一个强指针改为弱指针即可。
C++新特性21_shared_ptr与weak_ptr(大量使用,仅看此篇即可;使用方法;注意事项:不能用同一指针去初始化两个shared_ptr;循环引用问题;weak_pt用于解决循环引用问题)_第8张图片
此时,两个内部指针均会得到释放。
C++新特性21_shared_ptr与weak_ptr(大量使用,仅看此篇即可;使用方法;注意事项:不能用同一指针去初始化两个shared_ptr;循环引用问题;weak_pt用于解决循环引用问题)_第9张图片
原因是,弱指针的引用不会增加原来的引用计数,那么就使得引用不再是闭环,所以在出作用域之后,全部得到释放。

2. weak_ptr的使用

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

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

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

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

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

你可能感兴趣的:(#,C++新特性37篇,c++)