C++ shared_ptr&&weak_ptr的简单介绍和仿写

Posted Jqivin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ shared_ptr&&weak_ptr的简单介绍和仿写相关的知识,希望对你有一定的参考价值。


shared_ptr

一、shared_ptr的简单介绍

  1. shared_ptr是一个引用计数的智能指针,允许多个指针指向同一个对象。用引用计数来管理对象的资源。当用一个shared_ptr指针去初始化另一个shared_ptr指针的时候,引用计数会加一。当某一个shared_ptr生命周期结束,引用计数会减一。当引用计数为0 的时候,shared_ptr所指向的资源会被释放,引用计数器也会被释放。
  2. 结构
    在这里插入图片描述

二、shared_ptr的使用

1.函数介绍

①unique()函数:用来判断shared_ptr是否是资源的唯一拥有者。
②use_count()函数:检查引用计数。
③get()函数:得到shared_ptr所指向的堆区资源。
④reset()函数:重置shared_ptr.参数为一个原生指针
⑤swap()函数:交换两个shared_ptr。
主要函数如下图:
在这里插入图片描述
在这里插入图片描述

2.使用

shared_ptr的构造函数是不能进行隐式转换的。使用了explicit关键字。

shared_ptr<vector<int>> vc  = new vector<int>({1,2,3}); //编译错误
shared_ptr<vector<int>> vc (new vector<int>({1,2,3}));  //正确
class Object
{
private:
	int value;
public:
	Object(int val = 0) :value(val) { cout << "construct Object" << endl; }
	~Object() { cout << "destruct Object" << endl; }
	int Value() { return value; }
	const int  Value() const { return value; }
};
int main()
{
	shared_ptr<Object> obj(new Object(10));
	cout << obj->Value() << (*obj).Value() << endl;  //10 10
	shared_ptr<Object> obj2(new Object(20));
	cout << obj2->Value() << endl;  //20
	obj2 = obj;
	cout << obj2->Value() << endl; //10
	shared_ptr<Object> obj3(obj2);
	cout << obj3->Value() << endl; //10
	cout << obj.use_count() << endl;  //3
	obj3.reset(new Object(30)); 
	cout << obj3->Value() << "  obj3.use_count():" << obj3.use_count() << "  obj.use_count:" << obj.use_count() << endl;
	//          30                                        1                                           2
	obj3.swap(obj2);
	cout << obj2->Value() << "  obj2.use_count():" << obj2.use_count() << "  obj3.use_count:" << obj3.use_count() << endl;
	// 30                                                    1                                           2

	return 0;
}

结果:
在这里插入图片描述

三、shared_ptr对象创建方法的讨论

1. 有两种常见的创建的方法:

(1)调用shared_ptr的构造函数创建
(2)调用make_shared函数创建。这个函数也是定义在memory头文件中。

shared_ptr<Object> obj1(new Object(10));
shared_ptr<Object> obj2 = make_shared<Object>(100);
shared_ptr<string> s = make_shared<string>("aaaa");

关于构造函数的创建就不必多说了,shared_ptr是非侵入式的,当传入的指针不为空时,在构造函数中会执行在堆区创建一块空间的操作,这块空间就是引用计数的空间,所以引用计数的空间和shared_ptr指向的资源并不在一块。所以这就发生了两次内存的分配。但是内存的分配和回收是C++最慢的单次操作了
而make_shared函数的创建规则是:合并这两块空间为一块,即可以同时为计数器和原生内存分配空间。一次就把他们创建完成,并且在一块释放。即把二者的内存视为一个整体进行管理
在这里插入图片描述

2. 有关make_shared函数

1. make_shared的优点:
(1)减少了单次分配内存的次数
(2)增大cache的局部性:计数器课原生内存紧邻,所以这就减少了一般的cachemiss。
(3)异常的安全性比另一种方案更优。
看下面的代码:如果采用构造函数创建的方法。这个dosomething函数的参数的构建顺序是,先new Object{1024}这个空间,然后看第二个参数是否抛出异常,如果抛出异常就执行return 0。所以这就导致构建的对象得不到释放。(因为智能指针还没有构建,只是开辟了原生内存)。但是如果使用make_shared函数来进行,就比较好了。开辟这两块空间是一起进行的,如果抛出异常就都不创建。
在这里插入图片描述
2. make_shared 的缺点:
可能会存在对象析构了,但空间没有释放的问题。
如下图所示,当在链表中使用智能指针的时候,因为开辟的是连续的空间,只有mcnt_s == 0 && mcnt_w == 0&& mptr == nullptr才能释放这一大块空间,但是这种情况下,Object对象已经析构掉了(执行了objlist.pop_front()),但是wp还在生存期,所以造成了这一大块空间要一直存在,直到weak_ptr的对象wp生存期结束之后,mcnt_w的值为0,才会释放这一大块空间。
在这里插入图片描述

weak_ptr

一、weak_ptr的简单介绍

weak_ptr是配合shared_ptr使用的一个智能指针。它指向shared_ptr管理的对象,但是不影响对象的生命周期,不会改变shared_ptr的引用计数。
无论是否有weak_ptr指向,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。

二、weak_ptr的使用

1.成员函数介绍

在这里插入图片描述
(1)lock()函数:获得一个shared_ptr。
(2)expired():判断所指对象是否已经被销毁。
(3)reset():重置
(4)use_count():当前的引用计数值。

2. 使用

最常用的两个方法是lock()expired();

class Object
{
private:
	int value;
public:
	Object(int val = 0) :value(val) { cout << "construct Object" << endl; }
	~Object() { cout << "destruct Object" << endl; }
	int Value() { return value; }
	const int  Value() const { return value; }
};
int main()
{
	shared_ptr<Object> objs1(new Object(10));
	shared_ptr<Object> objs2(new Object(20));

	weak_ptr<Object> objw(objs1);
	cout << objw.use_count() << endl; //1
	
	shared_ptr<Object> objs3= objw.lock();           //lock()
	cout << objs1.use_count() << endl; //2

	objs1.reset();
	if (objw.expired())
		cout << "1过期" << endl;
	objs3.reset();
	if (objw.expired())
		cout << "2过期" << endl;
	return 0;
}

结果:
在这里插入图片描述
首先,进入主函数之后,构建了两个对象,然后objw指向objs1,之后用objs3.lock() (用objs1构建的一个智能指针)来构建objs3,所以,objs1的引用计数加一变成2,然后objs1重置为空,引用计数减一,这时候objw指向的对象还没有被析构,所以不会打印“1过期”,当重置objs3位空时,引用计数变为0,对象被析构(打印destruct Object),所以objw指向的对象被析构,所以打印“2过期”。最后打印的“destrcut Object“是objs2生命周期结束的时候调用析构函数,判断引用计数为1,减一之后为0,把对象析构掉。

关于shared_ptr造成的循环引用以及解决

1.循环引用

代码如下:

#include<iostream>
using namespace std;

class child;
class parent
{
public:
	shared_ptr<child> c;
	parent(){ cout << "parent construct" << endl; }
	void fun() { cout << "parent" << endl; }
	~parent() { cout << "~parent" << endl; }
};
class child
{
public:
	shared_ptr<parent> p;
	child() { cout << "child construct" << endl; }
	void fun() { cout << "chile" << endl; }
	~child() { cout << "~child" << endl; }
};
int main()
{
	shared_ptr<parent> par(new parent());
	shared_ptr<child> pch(new child());
	par->c = pch;
	pch->p = par;
	par->fun(); pch->fun();
	return 0;
}

结果
在这里插入图片描述
由上面的结果可以看到,只调用了构造函数,对象一直都没有析构。这就造成了内存的泄露。这是因为Parent和Child对象内部,具有各自指向对方的 shared_ptr,加上 parent和child这两个shared_ptr,说明每个对象的引用计数都是2。当程序退出时,即使parent和child被销毁,也仅仅是导致引用计数变为了1,因此并未销毁Parent和Child对象。
在这里插入图片描述

2.解决方案

吧parent和child类里的智能指针换成weak_ptr可以解决。weak_ptr不会增加shared_ptr的引用计数。所以当执行下面两行代码时不会使得引用计数增加,所以,程序结束的时候,par和pch调用各自的析构函数,使得引用计数减一之后变为0,这时候就可以析构各自指向的对象了。
系统只管理栈区的对象,当函数执行结束的时候,系统自动调用par和pch的析构函数。堆区空间系统不管理,要程序员自己析构。

par->c = pch;	
pch->p = par;

修改代码:

weak_ptr<child> c;
weak_ptr<parent> p;

修改之后的执行结果如下:
在这里插入图片描述

shared_ptr和weak_ptr的仿写

在这里插入图片描述
在这里插入图片描述

1. 代码展示:

namespace jqw
{
	template<class T>
	class RefCnt
	{
	private:
		T* mPtr;
		std::atomic<int> mCnt_s;
		std::atomic<int> mCnt_w;
	public:
		RefCnt(T* ptr = nullptr) :mPtr(ptr)
		{
			if (mPtr != nullptr)
			{
				mCnt_s = 1;
				mCnt_w = 0;
			}
		}
		~RefCnt() {}
		int getRef_s() { return mCnt_s; }
		void addRef_s() { ++mCnt_s; }
		int delRef_s() { return --mCnt_s; }

		int getRef_w() { return mCnt_w; }
		void addRef_w() { ++mCnt_w; }
		int delRef_w() { return --mCnt_w; }
	};
	template <class T> class weak_ptr;
	//删除器
	template <class T>
	struct MyDeletor
	{
	public:
		void operator()(T* ptr) const
		{
			delete ptr;
		}
	};

	//Deletor默认为MyDeletor<T>
	template<class T, typename Deletor = MyDeletor<T> >
	class shared_ptr
	{
	private:
		T* mPtr;
		RefCnt<T>* mpRefCnt;
		Deletor myDeletor;
	public:
		shared_ptr(T* ptr = nullptr) :mPtr(ptr), mpRefCnt(nullptr)
		{
			if (mPtr != nullptr)
			{
				mpRefCnt = new RefCnt<T>(mPtr);
			}
		}
		~shared_ptr()
		{
			if (mPtr != nullptr && 0 == mpRefCnt->delRef_s() )
			{
				myDeletor(mPtr);
				if (mpRefCnt->getRef_w() == 0)
				{
					delete mpRefCnt; //
				}
			}
			mPtr = nullptr;
			mpRefCnt = nullptr;
		}
		T& operator*()const { return *mPtr; }
		T* operator->() { return mPtr; }

		//拷贝构造,本身还没有被构建对象
		shared_ptr(const shared_ptr<T>& src) :mPtr(src.mPtr), mpRefCnt(src.mpRefCnt)
		{
			//传入的智能指针的自愿如果是空的话,不会new RefCnt对象
			if (mPtr != nullptr)
			{
				mpRefCnt->addRef_s();
			}
		}
		shared_ptr& operator=(const shared_ptr<T>& src)
		{
			if (&src == this) return *this;
			if (mPtr != nullptr && 0 == mpRefCnt->delRef_s())
			{
				myDeletor(mPtr);
				mPtr = nullptr;
				myDeletor(mPtr);
				if (mpRefCnt->getRef_w() == 0)    //这个要进行判断,不然使用weak_ptr的expired函数会出现问题
				{
					delete mpRefCnt; //
				}
				mpRefCnt = nullptr;
			}
			mPtr = src.mPtr;
			mpRefCnt = src.mpRefCnt;
			//必须要判断,因为传入的智能指针的自愿如果是空的话,不会new RefCnt对象
			if (mPtr != nullptr)
			{
				mpRefCnt->addRef_s();
			}
			return *this;
		}
		shared_ptr(shared_ptr<T>&& src)
		{
			mPtr = src.mPtr;
			mpRefCnt = src.mpRefCnt;
			src.mPtr = nullptr;
			src.mpRefCnt = nullptr;
		}
		shared_ptr<T>& operator=(shared_ptr<T>&& src)
		{
			if (mPtr != nullptr && 0 == mpRefCnt->delRef_s())
			{
				myDeletor(mPtr);
				mPtr = nullptr;
				if (mpRefCnt->getRef_w() == 0)
				{
					delete mpRefCnt; //
				}
				mpRefCnt = nullptr;
			}
			mPtr = src.mPtr;
			mpRefCnt = src.mpRefCnt;
		}
		// int use_count() { return mpRefCnt->getRef_s(); } error
		int use_count()
		{
			if (mPtr != nullptr)
			{
				return mpRefCnt->getRef_s();
			}
			return 0;
		}
		operator bool() const
		{
			return (mPtr != nullptr);
		}
		void reset(T* p = nullptr)
		{
			if (mPtr != nullptr && 0 == mpRefCnt->delRef_s())
			{
				myDeletor(mPtr);
				mPtr = nullptr;
				if (0 == mpRefCnt->getRef_w())
				{
					delete mpRefCnt;
				}
				mpRefCnt = nullptr;
			}
			mPtr = p;
			mpRefCnt = nullptr; //一定要写
			if (mPtr != nullptr)
			{
				mpRefCnt = new RefCnt(mPtr);
			}
		}
		void swap(shared_ptr<T>& src)
		{
			std::swap(mPtr, src.mPtr);
			std::swap(mpRefCnt, src.mpRefCnt);
		}
		shared_ptr(const weak_ptr<T>& _w)
		{
			mPtr = _w.mPtr;
			mpRefCnt = _w.mpRefCnt;
			mpRefCnt->addRef_s();
		}
		friend class weak_ptr<T>;
	};

	template <class T>
	class weak_ptr
	{
	private:
		T* mPtr;
		RefCnt<T>* mpRefCnt;
		void release()
		{
			if (mpRefCnt != nullptr)
			以上是关于C++ shared_ptr&&weak_ptr的简单介绍和仿写的主要内容,如果未能解决你的问题,请参考以下文章

C++笔记-auto_ptr&unique_ptr&shared_ptr&shared_ptr基本用法

C++笔记-auto_ptr&unique_ptr&shared_ptr&shared_ptr基本用法

深入了解C++ (15) | 源码分析auto_ptr & unique_ptr 设计

shared_ptr 和 unique_ptr 构造函数

C++ error: ‘shared_ptr’ was not declared in this scope

C++ error: ‘shared_ptr’ was not declared in this scope