【C++11】智能指针

目录

 1.RAII

1.1什么是RAII

1.2RAII的原理

1.3RAII的好处

2.auto_ptr

3.unique_ptr

 4.shared_ptr

4.1线程安全问题

4.2循环引用

4.3weak_ptr 


 

 1.RAII

1.1什么是RAII

RAII(Resource Acquisition IInitialization)是由c++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化,他是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术

1.2RAII的原理

资源的使用一般经历三个步骤a.获取资源 b.使用资源 c.销毁资源,但是资源的销毁往往是程序员经常忘记的一个环节,所以程序界就想如何在程序员中让资源自动销毁呢?c++之父给出了解决问题的方案:RAII(实现资源的自动销毁)它充分的利用了C++语言局部对象自动销毁的特性来控制资源的生命周期,实际上把管理一份资源的责任托管给了一个对象

1.3RAII的好处

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

给一个简单的例子来看下局部对象的自动销毁的特性:

#include 
#include 
#include 
using namespace std;

//#include "SmartPtr.h"

// 使用RAII思想设计的SmartPtr类
template
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _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 sp1(new int);
	SmartPtr sp2(new int);
	cout << div() << endl;
}
int main()
{
	try {
		Func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

【C++11】智能指针_第1张图片

但是这样类还不能封装好智能指针,因为他没有和指针一样的行为

要支持*和->

 template
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
private:
	T* _ptr;
};
struct Date
{
	int _year=1;
	int _month=1;
	int _day=1;
};
int main()
{
	SmartPtr sp1(new int);
	*sp1 = 10;
	cout << *sp1 << endl;
	SmartPtr sparray(new Date);
	// 需要注意的是这里应该是sparray.operator->()->_year = 2018;
	// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
	sparray->_year = 2018;
	sparray->_month = 1;
	sparray->_day = 1;
	return 0;
}

 

【C++11】智能指针_第2张图片 

 

【C++11】智能指针_第3张图片 

 

总结一下智能指针的原理
1. RAII特性
2. 重载operator*和opertaor->,具有像指针一样的行为

2.auto_ptr

【C++11】智能指针_第4张图片 首先是最遭受骂名的指针,查阅文档发现这个指针甚至被明确表明不适用于C++11,因为它存在线程安全的问题,这个指针最大的特点就是管理权悬空

 该指针在C++98中已经提出【C++11】智能指针_第5张图片

 实现如下:
 

namespace wrt
{
	template
	class auto_ptr //管理权转移
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{
			cout << "	auto_ptr(T * ptr)" << endl;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		auto_ptr(auto_ptr& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}
		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "~auto_ptr()" << endl;
				delete _ptr;
			}
		}
	private:
		T* _ptr;
	};
	void test_auto_ptr()
	{
		auto_ptr ap1(new int(1));
		auto_ptr ap2(ap1);
		//*ap1 = 10; //此时ap1悬空导致不能访问
		*ap2 = 20;
		cout << *ap2 << endl;
	}
}

由于使用ap1拷贝构造ap2,导致

auto_ptr(auto_ptr& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}

此时的原指针即ap1被悬空(置位nullptr)所以.....他就废掉了不能进行访问

3.unique_ptr

【C++11】智能指针_第6张图片 

注意下头文件,并且看到他用了两个类,第二个好像和删除有关(是删除器,在shared_ptr一起讲)

他是一个简单粗暴的用类封装的指针,行为和指针一样的,但是他不支持拷贝——防拷贝

 

template
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr) {}
		T& operator*()
		{
			return *_ptr;
		}
		T& operator->()
		{
			return _ptr;
		}
		//简单粗暴,防止拷贝
		//C++11
		unique_ptr(const unique_ptr& uq) = delete;
		unique_ptr& operator=(const unique_ptr& uq) = delete;
		//C++98
	//private:
	//	unique_ptr(const unique_ptr& uq);
		~unique_ptr()
		{
			if (_ptr)
				std::cout << "delete:" << _ptr << std::endl;

			delete _ptr;
		}
	private:
		T* _ptr;
	};
	void test_unique_ptr()
	{
		unique_ptr uq(new int(1));

	}

 4.shared_ptr

【C++11】智能指针_第7张图片 头文件还是

他和前面unique_ptr的区别就是他支持拷贝,并且提供引用计数

首先明确一个资源可能是被多个指针指向的,所以对于资源来说,要求支持引用计数——指向他的指针个数

引用计数本质就是一个int*指针

 尤其要注意shared_ptr的拷贝赋值函数,正常的指针就是支持拷贝赋值的

相同资源之间的赋值就没有实现的必要,现实场景中一般没有这个情况

shared_ptr& operator=(const shared_ptr& sp)
		{
			//相同的资源之间相互赋值不建议写
			//if(&sp != this) //因为可能本身就不同的对象但是管理着相同的资源,不用对象去判断,用资源判断

			if (sp._ptr != _ptr) //说明不是一个资源
			{
				Rease();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				Addcount();
			}
			return *this;
		}
	void Rease()
		{
			if ((--(*_pcount)) == 0) //此时要进行资源释放
			{
				if (_ptr)
				{
					cout << "delete:" <<_ptr<< endl;
					delete _ptr;
				}
				delete _pcount; //引用计数也应该释放
			}
		}

 首先看一个雏形

template
	class shared_ptr
	{
	public:
		shared_ptr( T*ptr=nullptr, int* pcount=new int(1))
			:_ptr(ptr),_pcount(pcount)
		{}

		//因为后面多次涉及析构,所以封装成函数
		void Rease()
		{
			if ((--(*_pcount)) == 0)
			{
				if (_ptr)
				{
					cout << "delete:" <<_ptr<< endl;
					delete _ptr;
				}
				delete _pcount;
			}
		}
		~shared_ptr()
		{
			Rease();
		}
		void Addcount()
        {
			(*_pcount)++;
			
		}
		//拷贝构造
		shared_ptr(const shared_ptr& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			Addcount();
		}

		shared_ptr& operator=(const shared_ptr& sp)
		{
			//相同的资源之间相互赋值不建议写
			//if(&sp != this) //因为可能本身就不同的对象但是管理着相同的资源,不用对象去判断,用资源判断

			if (sp._ptr != _ptr) //说明不是一个内存
			{
				Rease();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				Addcount();
			}
			return *this;
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		T* get_ptr() const 
		{
			return _ptr;
		}

		int use_count() const 
		{
			return *_pcount;
		}
	private:
		T* _ptr;
		int* _pcount;
	};

4.1线程安全问题

显然这个是有线程安全问题

void Rease()
		{
			if ((--(*_pcount)) == 0)
			{
				if (_ptr)
				{
					cout << "delete:" <<_ptr<< endl;
					delete _ptr;
				}
				delete _pcount;
			}
		}

就这个释放函数而言

假设t1和t2都进入了第一个if ,t1首先进入判断,但是没有删除就被切走,此时t2判断第二个if依然满足,进入释放_ptr,等到t1回来重复释放了_ptr

想控制引用计数的方法:

1.加锁 2.用automic,但是这样做非常的复杂

加锁浅浅实现一下

	template
	class shared_ptr
	{
	public:
		shared_ptr( T*ptr=nullptr, int* pcount=new int(1))
			:_ptr(ptr),_pcount(pcount),_pmtx(new mutex)
		{}

		//定时删除器 用function进行包装
		template
		shared_ptr(T* ptr , D del)
			:_ptr(ptr), _pcount( new int(1)), _pmtx(new mutex)
		{}

		//因为后面多次涉及析构,所以封装成函数
		void Rease()
		{
			_pmtx->lock();
			if ((--(*_pcount)) == 0)
			{
				if (_ptr)
				{
					cout << "delete:" <<_ptr<< endl;
					delete _ptr;
				}
				delete _pcount;
				//如何解决 delete _pmtx
			}
			_pmtx->unlock();
			
		}
		~shared_ptr()
		{
			Rease();
		}
		void Addcount()
		{
			_pmtx->lock();
			(*_pcount)++;
			_pmtx->unlock();
		}
		//拷贝构造
		shared_ptr(const shared_ptr& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			,_pmtx(sp._pmtx)
		{
			Addcount();
		}

		shared_ptr& operator=(const shared_ptr& sp)
		{
			//相同的资源之间相互赋值不建议写
			//if(&sp != this) //因为可能本身就不同的对象但是管理着相同的资源,不用对象去判断,用资源判断

			if (sp._ptr != _ptr) //说明不是一个内存
			{
				{
					Rease();
				}
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				_pmtx = sp._pmtx;
				Addcount();
			}
			return *this;
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		T* get_ptr() const 
		{
			return _ptr;
		}

		int use_count() const 
		{
			return *_pcount;
		}
	private:
		T* _ptr;
		int* _pcount;
		std::mutex* _pmtx;
	};

加锁还是有一个问题 当计数减为0时对_pcount释放,还要释放锁,但是还没解锁呐!!!

要解决这个问题就要加一个bool 标志位

        void Rease()
		{
			_pmtx->lock();
			bool daleteflage = false; //默认是没有删除的
			if ((--(*_pcount)) == 0)
			{
				if (_ptr)
				{
					cout << "delete:" <<_ptr<< endl;
					//delete _ptr;
				}
				delete _pcount;
				//如何解决 delete _pmtxx->用一个bool标志位
				daleteflage = true;
			}
			_pmtx->unlock();

			if (daleteflage)
				delete _pmtx;
			
		}

 现在看起来引用计数肯定是没问题了

使用两个线程测试一下吧

    class Date
	{
	public:
		Date() {}
		Date(int year, int month, int day)
			:_year(year), _month(month), _day(day) {}
		~Date()
		{}
		Date(const Date& d)
		{
			_month = d._month;
			_day = d._day;
			_year = d._year;
		}
	public:
		int _year = 0;
		int _month = 0;
		int _day = 0;
	};
    void SharePtrFunc(wrt::shared_ptr& sp, size_t n, mutex& mtx)
	{
		for (size_t i = 0; i < n; ++i)
		{
			// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
			wrt::shared_ptr copy(sp);
			// 智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n
			//次,但是最终看到的结果,并一定是加了2n
			{
			mtx.lock();
			copy->_year++;
			copy->_month++;
			copy->_day++;
			mtx.unlock();
			}
		}
	}
    void test_shared_safe()
	{
		wrt::shared_ptr p(new Date);
		cout << p.get_ptr() << endl;
		const size_t n = 100000;
		mutex mtx;
		thread t1(shareptrfunc,ref(p), n, ref(mtx));
		thread t2(shareptrfunc, ref(p), n, ref(mtx));
		t1.join();
		t2.join();
		cout << p.use_count() << endl;

		cout << p->_year << endl;
		cout << p->_month << endl;
		cout << p->_day << endl;
	}

智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但是最终看到的结果,并一定是加了2n,并且每次运行的结果都不一样

显然就是出现了线程安全的问题,但是刚才引用计数是没问题的啊

shared_ptr本身是线程安全的,因为引用计数是加锁保护的,但是shared_ptr管理的对象是不是安全的?不是 (根据上个例子可知)

把命名空间改成std发现结果还是一样的不对!

说明他管理的对象就是不安全的

4.2循环引用

在特定的场景下,ListNode中可能会在互相指向过程中抛异常,导致无法释放

struct ListNode
	{
		shared_ptr _next; //要注意智能指针必须有默认构造函数(库里也有)
		shared_ptr _prev;
		int _val=10;
		~ListNode()
		{
			cout << "~ListNode" << endl;
		}
	};
	void test_shared_cycle()
	{
		wrt::shared_ptr n1 = new ListNode;//其实这样写是不对的,相当于ListNode*指针隐式型转换,库里面在带参构造的函数前面加上了explicit(不允许隐式类型转换)
		wrt::shared_ptr n2 = new ListNode;
		
	}

 

库里面不支持这种转换

换种写法:

struct ListNode
	{
		shared_ptr _next; //要注意智能指针必须有默认构造函数(库里也有)
		shared_ptr _prev;
		int _val=10;
		~ListNode()
		{
			cout << "~ListNode" << endl;
		}
	};
	void test_shared_cycle()
	{
		wrt::shared_ptr n1(new ListNode);
	    wrt::shared_ptr n2(new ListNode); //此时有智能指针管就不用考虑释放
	}

 现在考虑指向的问题


		n1->_next = n2;
		n2->_prev = n1; //智能指针不能给给shared_ptr
		

只屏蔽一个指向会正常释放但是两个指向就什么都不会打印 是因为循环引用,本身shared_ptr设计导致极端场景的坑,分析一下场景(画图)

假设只让n1->next=n2;

【C++11】智能指针_第8张图片

释放_next之后_prev的引用计数减为1,此时释放_prev就可以正常释放资源

同理n2->_prev=n1;

两个指向就什么都不会打印,是因为如果要释放next的资源就要释放管着这个资源的prev,同理要释放prev就要释放管着prev资源的next 导致出现闭环——循环引用

循环引用导致内存泄漏,但是我还想保持指向只是不想让next和prev参与管理

4.3weak_ptr 

为了解决循环引用的问题,引入waek_ptr指针
 weak_ptr的特点是:
        //1.不符合RAII,不是常规智能指针
        //2.支持像指针一样,本质是专门设计出来解决shared_ptr循环应用的问题,不可以单独使用
        //3.weak_ptr本身是有引用计数的,但是他指向资源但不参与管理 

template
	class weak_ptr
	{
	public:
		weak_ptr() :_ptr(nullptr),_pcount(new int(1))
		{}
		weak_ptr(const shared_ptr& sp) :_ptr(sp.get_ptr()),_pcount(new int(sp.use_count())) {}
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
		T* get_ptr() const
		{
			return _ptr;
		}

		int use_count()
		{
			return *_pcount;
		}
	private:
		T* _ptr;
		int* _pcount;
	};

4.4定制删除器

shared_ptr释放特定类型的本质就是定制删除器,删除器就是可调用对象,包括:函数指针,lambda函数,仿函数


	//定制删除器
	template
	struct DeleteDate
	{
		void operator()(T* _ptr)
		{
			cout << "void operator()(T * _ptr)" << endl;
			delete[]_ptr;
		}
			T* _ptr;
	};
	void test_shared_deletor()
	{
		wrt::shared_ptr sp1(new Date[10],DeleteDate());
		wrt::shared_ptr sp2(new Date[10], [](Date* ptr) {
			cout << "lambda delete" << endl;
			delete[]ptr; });
		wrt::shared_ptr spF3(fopen("Test.cpp","r"), [](FILE* ptr) {
			cout << "fclose()" << endl;
			fclose(ptr); });
	

	}

把以上所讲的所有应用到shared_ptr的最终代码

namespace wrt
{
	template
	class shared_ptr
	{
	public:
		shared_ptr( T*ptr=nullptr, int* pcount=new int(1))
			:_ptr(ptr),_pcount(pcount),_pmtx(new mutex)
		{}

		//定时删除器 用function进行包装
		template
		shared_ptr(T* ptr , D del)
			:_ptr(ptr), _pcount( new int(1)), _pmtx(new mutex),_del(del)
		{}

		//因为后面多次涉及析构,所以封装成函数
		void Rease()
		{
			_pmtx->lock();
			bool daleteflage = false;
			if ((--(*_pcount)) == 0)
			{
				if (_ptr)
				{
					cout << "delete:" <<_ptr<< endl;
					//delete _ptr;
					_del(_ptr); //用包装器释放
				}
				delete _pcount;
				//如何解决 delete _pmtxx->用一个bool标志位
				daleteflage = true;
			}
			_pmtx->unlock();

			if (daleteflage)
				delete _pmtx;
			
		}
		~shared_ptr()
		{
			Rease();
		}
		void Addcount()
		{
			_pmtx->lock();
			(*_pcount)++;
			_pmtx->unlock();
		}
		//拷贝构造
		shared_ptr(const shared_ptr& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			,_pmtx(sp._pmtx)
		{
			Addcount();
		}

		shared_ptr& operator=(const shared_ptr& sp)
		{
			//相同的资源之间相互赋值不建议写
			//if(&sp != this) //因为可能本身就不同的对象但是管理着相同的资源,不用对象去判断,用资源判断

			if (sp._ptr != _ptr) //说明不是一个内存
			{
				{
					Rease();
				}
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				_pmtx = sp._pmtx;
				Addcount();
			}
			return *this;
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		T* get_ptr() const 
		{
			return _ptr;
		}

		int use_count() const 
		{
			return *_pcount;
		}
	private:
		T* _ptr;
		int* _pcount;
		std::mutex* _pmtx;
		function  _del = [](T* ptr) {
			cout << "lambda" << ptr << endl;
			delete ptr;
		};//包装器
	};
	class Date
	{
	public:
		Date() {}
		Date(int year, int month, int day)
			:_year(year), _month(month), _day(day) {}
		~Date()
		{}
		Date(const Date& d)
		{
			_month = d._month;
			_day = d._day;
			_year = d._year;
		}
	public:
		int _year = 0;
		int _month = 0;
		int _day = 0;
	};
	void SharePtrFunc(wrt::shared_ptr& sp, size_t n, mutex& mtx)
	{
		for (size_t i = 0; i < n; ++i)
		{
			// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
			wrt::shared_ptr copy(sp);
			// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n
			//次,但是最终看到的结果,并一定是加了2n
			{
			mtx.lock();
			copy->_year++;
			copy->_month++;
			copy->_day++;
			mtx.unlock();
			}
		}
	}
	template
	class weak_ptr
	{
	public:
		weak_ptr() :_ptr(nullptr),_pcount(new int(1))
		{}
		weak_ptr(const shared_ptr& sp) :_ptr(sp.get_ptr()),_pcount(new int(sp.use_count())) {}
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
		T* get_ptr() const
		{
			return _ptr;
		}

		int use_count()
		{
			return *_pcount;
		}
	private:
		T* _ptr;
		int* _pcount;
	};
	//void test_shared_safe()
	//{
	//	wrt::shared_ptr p(new Date);
	//	cout << p.get_ptr() << endl;
	//	const size_t n = 100000;
	//	mutex mtx;
	//	thread t1(shareptrfunc,ref(p), n, ref(mtx));
	//	thread t2(shareptrfunc, ref(p), n, ref(mtx));
	//	t1.join();
	//	t2.join();
	//	cout << p.use_count() << endl;

	//	cout << p->_year << endl;
	//	cout << p->_month << endl;
	//	cout << p->_day << endl;
	//}
	void test_shared_ptr()
	{
		/*shared_ptr sq1(new int(1));
		shared_ptr sq2(sq1);*/
		shared_ptr day1(new Date(2023, 7, 4));
		shared_ptr day2(day1);
		shared_ptr day3(new Date(2022,4,4));
		day3 = day1;
	}


	struct ListNode
	{/*
		ListNode* _next;
		ListNode* _prev;*/
		shared_ptr _next; //但是这样会报错说没有默认构造函数,是因为只能指针没有默认构造函数
		shared_ptr _prev;
		//wrt::weak_ptr _next;
		//wrt::weak_ptr _prev;

		int _val=10;
		~ListNode()
		{
			cout << "~ListNode" << endl;
		}
	};
	void test_shared_cycle()
	{
		//ListNode* n1 = new ListNode;
		//ListNode* n2 = new ListNode;
		//n1->_next = n2;  //但是可能这里会抛异常,这样就无法正常释放,所以考虑使用只能指针
		//n2->_prev = n1;
		//delete n1;
		//delete n2;
		//wrt::shared_ptr n1 = new ListNode;//其实这样写是不对的,相当于ListNode*指针隐式型转换,库里面在带参构造的函数前面加上了explicit 
		//wrt::shared_ptr n2 = new ListNode;
		//wrt::shared_ptr n1(new ListNode);
	   // wrt::shared_ptr n2(new ListNode); //此时有智能指针管就不用考虑释放
		//现在考虑指向的问题
		//n1->_next = n2;
		//n2->_prev = n1; //智能指针不能给给shared_ptr
		//只屏蔽一个指向会正常释放但是两个指向就什么都不会打印 是因为循环引用,本身shared_ptr设计导致极端场景的坑,分析一下场景(画图)
		//循环引用导致内存泄漏,但是我还想保持指向只是不想让next和prev参与管理
		//为了解决循环引用的问题,引入waek_ptr指针
		//他的特点是:
		//1.不符合RAII,不是常规智能指针
		//2.支持像指针一样,本质是专门设计出来解决shared_ptr循环应用的问题
		//3.weak_ptr本身是有引用计数的,但是他指向资源但不参与管理
	}

	//定制删除器
	template
	struct DeleteDate
	{
		void operator()(T* _ptr)
		{
			cout << "void operator()(T * _ptr)" << endl;
			delete[]_ptr;
		}
			T* _ptr;
	};
	void test_shared_deletor()
	{
		wrt::shared_ptr sp1(new Date[10],DeleteDate());
		wrt::shared_ptr sp2(new Date[10], [](Date* ptr) {
			cout << "lambda delete" << endl;
			delete[]ptr; });
		wrt::shared_ptr spF3(fopen("Test.cpp","r"), [](FILE* ptr) {
			cout << "fclose()" << endl;
			fclose(ptr); });
	

	}
}

你可能感兴趣的:(c++,开发语言)