智能指针11

Posted 蚍蜉撼树谈何易

tags:

篇首语:本文由小常识网(cha138.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 (以上是关于智能指针11的主要内容,如果未能解决你的问题,请参考以下文章

[C++11]shared_ptr共享智能指针的初始化与使用

[C++11]共享智能指针shared_ptr指定删除器

在条件或循环中分配智能指针

智能指针11

智能指针11

智能指针11