C++11 — 智能指针

Posted 晚风不及你的笑427

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++11 — 智能指针相关的知识,希望对你有一定的参考价值。

目录

1. 为什么需要智能指针?

2. 内存泄漏

 2.1 什么是内存泄漏,内存泄漏的危害

 2.2 内存泄漏分类

 2.3 如何避免内存泄漏

3.智能指针的使用及原理

 3.1 RAII

 3.2 std::auto_ptr

 3.3 std::unique_ptr

 3.4 std::shared_ptr

std::shared_ptr的循环引用

4.C++11和boost中智能指针的关系


 

1. 为什么需要智能指针?

我们先看一看下面这段代码会不会出现什么问题?

如果p1的new抛异常,那么不会有什么问题,直接main函数捕获即可,因为其他的语句还没开始执行;如果p2的new抛异常,那么会直接跳转到main函数执行流中找匹配类型的catch,由于没有匹配的类型,那么异常程序会终止;如果div函数内部抛异常,那么它会直接跳转到main函数中寻找该异常匹配的类型,然后去执行catch里的内容,而由于抛异常导致执行流跳过Func函数后面的部分,所以p1,p2指针指向的空间没有被释放,导致了内存泄漏。

int div()

	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;

void Func()

	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int;
	int* p2 = new int;
	cout << div() << endl;
	delete p1;
	delete p2;

int main()

	try
	
		Func();
	
	catch (exception& e)
	
		cout << e.what() << endl;
	
	return 0;

2. 内存泄漏

 2.1 什么是内存泄漏,内存泄漏的危害

  • 什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
  • 内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

 2.2 内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一
块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

  • 系统资源泄漏

指程序使用系统分配的资源,比如套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

 2.3 如何避免内存泄漏

1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
2. 采用RAII思想或者智能指针来管理资源。
3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结:
内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄
漏检测工具。


3.智能指针的使用及原理

 3.1 RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。也就是说资源的生命周期和对象的生命周期绑定了。

对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
        1.不需要显式地释放资源。
        2.采用这种方式,对象所需的资源在其生命期内始终保持有效。

我们就可以用这种方式去解决刚开始提出的问题。 

template<class T>
class SmartPtr

public:
	//RAII
	SmartPtr(T* ptr = nullptr)
		:_ptr(ptr)
	
	~SmartPtr()
	
		cout << _ptr << endl;
		if(_ptr)
			delete _ptr;
	
	//像指针一样
	T& operator*()
	
		return *_ptr;
	
	T* operator->()
	
		return _ptr;
	
	T& operator[](const int n)
	
		return *(_ptr + n);
	
private:
	T* _ptr;
;
int div()

	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;

void Func()

	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);

	*sp2 = 10;
	sp2[0]--;

	cout << *sp2 << endl;

	cout << div() << endl;
	

int main()

	try
	
		Func();
	
	catch (exception& e)
	
		cout << e.what() << endl;
	
	return 0;

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

我们这里虽然实现了智能指针,但是有一个严重的问题需要处理,如果我们将一个智能指针对象拷贝给另一个对象,因为我们没有实现拷贝构造,编译器会默认生成,他们两个指向同一块空间,析构时就会析构两次,那么就会出错。

那么有人说我们自己实现一个拷贝构造不就完了,答案是不行,如果只是深拷贝就简单了。这里我们的智能指针本就是模拟指针,那么两个指针指向同一块空间不是很正常吗?怎么办呢?

 3.2 std::auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。
那么auto_ptr是怎么解决这里的问题呢?他这里是资源管理权转移的思想,也就是说,本来两个智能指针对象不能同时指向一块空间,那么好,本来这块空间是我管理的,你把我拷贝给你,那么你就指向这块空间,我不指向了,我变成空了。这里如果不熟悉的人使用就会有空指针解引用的问题。

这个实现很简单,将外部传来的对象内的指针置空即可。

		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		
			sp._ptr = nullptr;
		

 我们用调试的方式去看,sp1指向的空间给了sp2后,sp1内的指针就成空指针了。

 结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr。

 3.3 std::unique_ptr

在C++11中又提供了几个智能指针。

那么unique_ptr是怎么解决这个问题的呢?它直接简单粗暴的防拷贝。

 直接使用C++11中的关键字delete。delete:禁止生成默认函数。

unique_ptr(const unique_ptr<T>& up) = delete;

 3.4 std::shared_ptr

那么shared_ptr是怎么解决这个问题的呢?是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
 

1.shared_ptr在其内部,给每个资源都维护了着一份计数器,用来记录该份资源被几个对象共
享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减
一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

 

	template<class T>
	class shared_ptr
	
	public:
		//RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		
		~shared_ptr()
		
			release();
		
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
		
			++(*_pcount);
		
		//sp1 = sp2
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		
			if (up._ptr != _ptr)
			
				release();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);
			
			return *this;
		
		void release()
		
			if (--(*_pcount) == 0)
			
				delete _ptr;
				delete _pcount;
			
		
		//像指针一样
		T& operator*()
		
			return *_ptr;
		
		T* operator->()
		
			return _ptr;
		
		T& operator[](const int n)
		
			return *(_ptr + n);
		
	private:
		T* _ptr;
		int* _pcount;
	;

通过这种方式解决了指针拷贝的问题,但是又存在了新的问题,如果多个线程同时使用一个智能指针,就会出现数据不一致问题。 

 看下图,我们用两个线程对sp1进行拷贝,析构,正常情况下引用计数应该是1,但是循环变大的情况下,引用计数出现了异常,甚至会出现报错情况,这是由于线程引起的线程安全问题,那么怎么处理呢?

 那么就需要线程在对引用计数++--的时候是互斥的,也就是说当前只能有一个线程进行++或--,那么就需要互斥锁对引用计数进行保护。当然我们要想让两个线程看到同一个锁,就需要让锁像引用计数一样开辟空间,让线程同时指向这个锁。线程会有一篇文章专门讲,这里就大概说一说。

template<class T>
	class shared_ptr
	
	public:
		//RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
			,_pmutex(new mutex)
		
		~shared_ptr()
		
			release();
		
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
			, _pmutex(sp._pmutex)
		
			_pmutex->lock();
			++(*_pcount);
			_pmutex->unlock();

		
		//sp1 = sp2
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		
			if (sp._ptr != _ptr)
			
				release();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				_pmutex = sp._pmutex;

				_pmutex->lock();
				++(*_pcount);
				_pmutex->unlock();
			
			return *this;
		
		void release()
		
			bool flag = false;
			_pmutex->lock();
			if (--(*_pcount) == 0)
			
				delete _ptr;
				delete _pcount;
				flag = true;
			
			_pmutex->unlock();
			if (flag)
			
				delete _pmutex;
			
		
		int Pcount()const
		
			return *_pcount;
		
		T* getPtr()const
		
			return _ptr;
		
		//像指针一样
		T& operator*()
		
			return *_ptr;
		
		T* operator->()
		
			return _ptr;
		
		T& operator[](const int n)
		
			return *(_ptr + n);
		
	private:
		T* _ptr;
		int* _pcount;
		mutex* _pmutex;
	;

那么此时相同的代码就不会出现之前的问题了。

 那么shared_ptr智能指针是线程安全的吗?

shared_ptr本身是线程安全的,这里的指的是在拷贝和析构的时候引用计数++--是线程安全的,但是它管理资源的访问不是线程安全的,需要使用的地方自行保护。

 加上锁后问题就解决了。

 但是shared_ptr的问题还没结束,还有一个致命的问题。

std::shared_ptr的循环引用

这个问题就是shared_ptr的循环引用,shared_ptr对象出了作用域调用析构函数本该释放对应的空间,但是却没有释放导致内存泄漏。

那么究竟是什么原因导致的呢?

正常的链表中有两个原生指针指向前一个节点和后一个节点,我们释放某个节点也就会将该节点和它的两个原生指针都从链表中拿走,不会有痕迹存在链表中。

而shared_ptr中,我们不能直接拿原生指针指向sp1或sp2,那么就需要prev和next都为shared_ptr对象才能像链表指针一样,才能去指向前一个或者后一个节点,那么问题就出现在这里,由于prev和next都为shared_ptr对象,那么sp1的next指向sp2时,sp2的引用计数就会变成2,同理sp2的prev指向sp1,sp1的引用计数也会变成2,等到析构sp1和sp2,只会将他们两个的引用计数--,因为引用计数并不是0,所以空间不会被释放。

由于next是sp1的成员,只有sp1的空间被释放了,next才会被析构,那么要想sp1的空间被释放,就必须让sp2的prev被析构,sp2的prev要想被析构,就必须释放sp2的空间,要想sp2的空间被释放,就必须析构sp1的next.......这样就陷入一个死循环中了,sp1和sp2的空间就都不会被释放了。

 那么怎么解决呢?

我们把shared_ptr的_prev和_next改成weak_ptr就可以了。

 

这里还有一个问题需要注意,我们new一个对象数组,会发现程序崩溃了,这是因为啥?

因为底层基本都是delete去释放,拿delete去释放数组空间(应该delete[]),由于不匹配当然会崩溃。

 那么怎么解决?

其实shared_ptr设计了一个定制删除器来解决这个问题,我们自己去显示的传入仿函数对象或者lambda表达式,这样就可以通过我们的方法去释放空间。

 不过这种方式只有库里的shared_ptr可以这样使用,我们自己实现的没有库里的那么复杂,所以要想实现这种方法,需要换一种方式。

 需要给shared_ptr多加一个模板参数,定义一个D类型的成员,再把release中释放空间的地方一换,当然需要提供一个默认的释放方式,不然的话,不显示传递释放方式这个shared_ptr就用不了。如果需要传递就传递,不需要就可以不传递。

//默认的释放方式    
    template<class T>
	class defualt_delete
	
	public:
		void operator()(T* ptr)
		
			delete ptr;
            cout << "delete :" << ptr << endl;//方便观看结果
		
	;
//需要给shared_ptr多加一个模板参数
	template<class T,class D = defualt_delete<T>>
	class shared_ptr
	
        ....//其他的可以不动,修改一下release
        void release()
		
			bool flag = false;
			_pmutex->lock();
			if (--(*_pcount) == 0)
			
				//delete _ptr;
				_del(_ptr);//可以用我们传的方式释放空间
				delete _pcount;
				flag = true;
			
			_pmutex->unlock();
			if (flag)
			
				delete _pmutex;
			
		
    private:
		T* _ptr;
		int* _pcount;
		mutex* _pmutex;
		D _del;
    ;

这样就都可以很好的使用了,不过需要传递自己的释放方式就得在参数的地方传了,那么lambda表达式就用不了。

 unique_ptr的方式是跟我们一样的。

 


4.C++11和boost中智能指针的关系

1. C++ 98 中产生了第一个智能指针auto_ptr.
2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost
的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

 完整代码:智能指针/智能指针/SmartPtr.h · 晚风不及你的笑/作业库 - 码云 - 开源中国 (gitee.com)

智能指针11

智能指针

智能指针存在的必要性

  1. malloc出来的空间,没有进行释放,存在内存泄漏的问题。
  2. 异常安全问题。如果在malloc和free之间如果存在抛异常,那么还是有内存 泄漏。这种问题就叫异常安全

智能指针的使用及原理

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效。

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

3. 解决浅拷贝的问题

C++98中的智能指针

auto_ptr:
edition1:
如何解决浅拷贝问题:资源转移。
缺点:转移资源,若此时想对原对象操作会出错。

namespace bit

	template<class T>
	class auto_ptr
	
      public:
	 auto_ptr(T* ptr=nullptr)
		 :_ptr(ptr)
	 
	 auto_ptr(auto_ptr<T>& pt):_ptr(pt._ptr)
	 
		 pt._ptr = nullptr;
	 
	 auto_ptr<T>& operator=(auto_ptr<T>& ap)
	 
		 if (this != &ap)
		 
			 // 此处需要将ap中的资源转移给this
			 // 但是不能直接转移,因为this可能已经管理资源了,否则就会造成资源泄漏
			 if (_ptr)
			 
				 delete _ptr;
			 

			 // ap就可以将其资源转移给this
			 _ptr = ap._ptr;
			 ap._ptr = nullptr;   // 让ap与之前管理的资源断开联系,因为ap中的资源已经转移给this了
		 

		 return *this;
	 
	 ~auto_ptr()
	 
		 if (_ptr)
		 
			 delete _ptr;
			 _ptr = nullptr;
		 
	 
	 T& operator*()
	 
		 return *_ptr;
	 
	 T* operator->()
	 
		 return _ptr;
	 
	 T* Get()
	 
		 return _ptr;
	 
	private:
		T* _ptr;
	;
;

editio2:转移释放权限

namespace bit

	template<class T>
	class auto_ptr
	
	public:
		auto_ptr(T* ptr =nullptr)
			:_ptr(ptr)
			,owner(false)
		
			if (_ptr)
			
				owner = true;
			
		
		auto_ptr(auto_ptr<T>& t)
			:_ptr(t._ptr)
			,owner(t.owner)
		
			t.owner = false;
		
		auto_ptr<T>& operator=(auto_ptr<T>& t)
		
			if (this != &t)
			
				if (this->_ptr && this->owner)
				
					delete _ptr;
				
				_ptr = t._ptr;
				owner = t.owner;
				t.owner = false;
			
			return *this;
		
		~auto_ptr()
		
			if (_ptr && owner)
			
				delete _ptr;
				owner = false;
				_ptr = nullptr;
			
		
		T& operator*()
		
			return *_ptr;
		
		T* operator->()
		
			return _ptr;
		
		T* Get()
		
			return _ptr;
		

	private:
		T* _ptr;
		bool owner;
	;

void TestAutoPtr()

	bit::auto_ptr<int> ap1(new int);
	*ap1 = 100;

	bit::auto_ptr<int> ap2(ap1);

	// 和我们对指针常规的认知有区别的
	int* p1 = new int;
	int* p2(p1);
	*p1 = 10;
	*p2 = 20;
	delete p1;
	p1 = p2 = nullptr;


	// auto_ptr采用资源转移的方式虽然将浅拷贝的问题解决了,但是引用了新的问题
	if (ap2.Get())
		*ap2 = 2000;
	if (ap1.Get())
		*ap1 = 1000;   // 代码会崩溃,因为ap1当中的资源已经转移走了,ap1当中的指针指向的空


	bit::auto_ptr<int> ap3(new int);
	*ap3 = 300;

	bit::auto_ptr<int> ap4(new int);
	*ap4 = 400;

	ap3 = ap4;

	//
	// 致命的缺陷---可以会导致野指针
	if (true)
	
		bit::auto_ptr<int> ap5(ap2);
		*ap5 = 100;
		*ap2 = 200;
		*ap1 = 300;

		// 再来开if的作用域时,ap5已经将管理的资源释放掉了
		// 而ap1和ap2根本就不知道,其内部的指针称为野指针了
	

	// 如果通过ap1和ap2再访问资源时,代码就会出问题
	*ap2 = 10;


int main()

	TestAutoPtr();
	return 0;

缺陷:可能会导致野指针的情况,比如上面的示例
所以:建议不要使用auto_ptr

C++11中的智能指针

C++11中unique_ptr指针

这个智能指针的作用就是防拷贝,既然拷贝可能会带来资源重复释放,资源泄露的问题,那我从根源上就不让你拷贝。

// unique_ptr: RAII + operator*()/operator->()+解决浅拷贝方式:禁止拷贝---资源独占
// 一份资源只能被一个对象来进行管理,对象之间不能共享资源

// 应用场景:只能应用与资源被一个对象管理 并且不会被共享的场景当中

// 缺陷:多个对象之间不能共享资源

// 负责释放new资源
template<class T>
class DFDef

public:
	void operator()(T*& ptr)
	
		if (ptr)
		
			delete ptr;
			ptr = nullptr;
		
	
;

// 负责:malloc的资源的释放
template<class T>
class Free

public:
	void operator()(T*& ptr)
	
		if (ptr)
		
			free(ptr);
			ptr = nullptr;
		
	
;

// 关闭文件指针
class FClose

public:
	void operator()(FILE*& ptr)
	
		if (ptr)
		
			fclose(ptr);
			ptr = nullptr;
		
	
;


namespace bite

	// T: 资源中所放数据的类型
	// DF: 资源的释放方式
	// 定制删除器
	template<class T, class DF = DFDef<T>>
	class unique_ptr
	
	public:
		/
		// RAII
		unique_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		

		~unique_ptr()
		
			if (_ptr)
			
				// 问题:_ptr管理的资源:可能是从堆上申请的内存空间、文件指针、malloc空间...
				// delete _ptr; // 注意:此处的释放资源的方式不能写死了,应该按照资源类型不同找对应的方式释放
				// malloc--->free
				// new---->delete
				// fopen--->fclose关闭
				DF df;
				df(_ptr);
			
		

		
		// 具有指针类似的行为
		T& operator*()
		
			return *_ptr;
		

		T* operator->()
		
			return _ptr;
		

		T* Get()
		
			return _ptr;
		

		
		// 解决浅拷贝方式---资源独占,即防止被拷贝

		// C++98
		/*
		   将拷贝构造函数以及赋值运算符重载方法只声明不定义,并且需要将其权限设置为私有的

		   // 只声明不定义,没有将权限设置为private---不行的:因为方法别人可以再类外定义
		*/
	private:
		unique_ptr(const unique_ptr<T, DF>&);
		unique_ptr<T, DF>& operator=(const unique_ptr<T, DF>&);

		// C++11: 可以让编译器不生成默认的拷贝构造以及赋值运算符重载---delete
		// 在C++11当中,delete关键字的功能扩展:释放new申请的空间  用其修饰默认成员函数,表明:编译器不会生成了
		// unique_ptr(const unique_ptr<T,DF>&) = delete;  // 表明:编译器不会生成默认的拷贝构造函数
		// unique_ptr<T,DF>& operator=(const unique_ptr<T,DF>&) = delete;// 表明:编译器不会生成默认的赋值运算符重载
	private:
		T* _ptr;
	;

	// 用户在外部可以对方法进行定义---在unique_ptr的类中如果将该权限设置为private的
	//template<class T>
	//unique_ptr<T>::unique_ptr(const unique_ptr<T>& up)
	//



#include <memory>

void TestUniquePtr()

	bite::unique_ptr<int> up1(new int);
	bite::unique_ptr<int, Free<int>> up2((int*)malloc(sizeof(int)));
	bite::unique_ptr<FILE, FClose> up3(fopen("12345.txt", "w"));


int main()

	TestUniquePtr();

	bite::unique_ptr<int> up1(new int);
	// bite::unique_ptr<int> up2(up1);

	bite::unique_ptr<int> up3(new int);

	// up1 = up3;

	///
	// 标准库
	unique_ptr<int> up4(new int);
	// unique_ptr<int> up5(up4);
	unique_ptr<int> up6(new int);
	// up4 = up6;
	return 0;

C’++11中的shared_ptr

// 负责释放new资源
template<class T>
class DFDef

public:
	void operator()(T*& ptr)
	
		if (ptr)
		
			delete ptr;
			ptr = nullptr;
		
	
;

// 负责:malloc的资源的释放
template<class T>
class Free

public:
	void operator()(T*& ptr)
	
		if (ptr)
		
			free(ptr);
			ptr = nullptr;
		
	
;

// 关闭文件指针
class FClose

public:
	void operator()(FILE*& ptr)
	
		if (ptr)
		
			fclose(ptr);
			ptr = nullptr;
		
	
;


#include <mutex>

namespace bite

	// shared_ptr: 自身才是安全的---加锁:为了保证shared_ptr自身的安全性
	template<class T, class DF = DFDef<T>>
	class shared_ptr
	
	public:
		//
		// RAII
		shared_ptr(T* ptr = nullptr)
			: _ptr(ptr)
			, _pCount(nullptr)
			, _pMutex(new mutex)
		
			if (_ptr)
			
				// 此时只有当前刚刚创建好的1个对象在使用该份资源
				_pCount = new int(1);
			
		

		~shared_ptr()
		
			Release();
		

		/
		// 具有指针类似的行为
		T& operator*()
		
			return *_ptr;
		

		T* operator->()
		
			return _ptr;
		

		T* Get()
		
			return _ptr;
		

		//
		// 用户可能需要获取引用计数
		int use_count()const
		
			return *_pCount;
		

		///
		// 解决浅拷贝方式:采用引用计数
		shared_ptr(const shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pCount(sp._pCount)
			, _pMutex(sp._pMutex)
		
			AddRef();
		

		shared_ptr<T, DF>& operator=(const shared_ptr<T, DF>& sp)
		
			if (this != &sp)
			
				// 在和sp共享之前,this先要将之前的状态清空
				Release();

				// this就可以和sp共享资源以及计数了
				_ptr = sp._ptr;
				_pCount = sp._pCount;
				_pMutex = sp._pMutex;
				AddRef();
			

			return *this;
		

	private:
		void AddRef()
		
			if (nullptr == _ptr)
				return;

			_pMutex->lock();
			++(*_pCount);
			_pMutex->unlock();
		

		void Release()
		
			if (nullptr == _ptr)
				return;

			bool isDelete = false;
			_pMutex->lock();

			if (_ptr && 0 == --(*_pCount))
			
				// delete _ptr;
				DF df;
				df(_ptr);
				delete _pCount;
				_pCount = nullptr;
				isDelete = true;
			

			_pMutex->unlock();

			if (isDelete)
			
				delete _pMutex;
			
		
	private:
		T* _ptr;        // 用来接收资源的
		int* _pCount;   // 指向了引用计数的空间---记录的是使用资源的对象的个数
		mutex* _pMutex; // 目的:保证对引用计数的操作是安全的
	;



void TestSharedPtr()

	bite::shared_ptr<int> sp1(new int);
	bite::shared_ptr<int> sp2(sp1);

	bite::shared_ptr<int> sp3(new int);
	bite::shared_ptr<int> sp4(sp3);

	sp3 = sp2;   // sp2和sp3共享同一份资源了

	sp4 = sp2;   // sp2和sp4共享同一份资源了



// 这个语法也是C++11新的语法:在定义成员变量时,可以就地初始化
struct A

	int a = 0;
	int b = 0;
	int c = 0;
;


// 线程函数
void ThreadFunc(bite::shared_ptr<A>& sp, int n)

	for (int i = 0; i < n; ++i)
	
		bite::shared_ptr<A> copy(sp);
		copy->a++;
		copy->b++;
		copy->c++;
	



#include <thread>

void TestSharedPtrSafe()

	bite::shared_ptr<A> sp(new A);

	// t1对象将来就和对应的线程绑定一起了
	thread t1(ThreadFunc, sp, 10000);
	thread t2(ThreadFunc, sp, 10000);

	// 线程等待
	t1.join();
	t2.join();


	// 1. 代码会不会崩溃----->shared_ptr是否安全---没有崩溃:
	// 2. A 对象中的各个成员的值是不是都是20000
	cout << sp->a << endl;
	cout << sp->b << endl;
	cout << sp->c << endl;




/
// 利用标准库中的shared_ptr来进行测试
#include <memory>
void ThreadFuncstd(shared_ptr<A>& sp, int n)

	for (int i = 0; i < n智能指针11

智能指针11

C++11 unique_ptr智能指针详解

C++11 智能指针和多态性

C++11 — 智能指针

C++11 weak_ptr智能指针(一看即懂)