C++---智能指针原理讲解

为什么需要智能指针

在写代码的时候,从堆上申请的空间,由于一些原因(代码中途异常),没有得到释放,从而导致代码出现内存泄漏,所以为了防止此类问题的出现,从而出现智能指针。采用智能指针可以自动检测,指针如果不用,则会自动释放堆上的空间。

智能指针作用
管理指针,自动释放资源。
在这里插入图片描述

RAII

RAII是一种利用对象生命周期来控制程序资源的技术

在类中,对象构造的时候获取资源,最后在对象析构后,释放资源,这样就不用担心内存资源没有被释放。如果将申请资源的指针交给类来管理(管理:释放资源),就在该函数结束时,编译器自动调用类的析构函数,完成对类管理资源的释放。实际上就是将指针交给类进行管理,在构造函数中申请资源,在析构函数中,释放资源。

  • 优点
    不用显示释放资源
    对象在使用中资源始终有效

在这里插入图片描述
同时RAII,还需要让该类具有像指针一样的方式,只需要对* 与->两个运算符重载。
缺点:RAII存在浅拷贝问题。

template<class T>
class Smartptr
{
    
    
public:
	Smartptr(T *ptr = nullptr)
		:_ptr(ptr)
	{
    
    }
	~Smartptr()
	{
    
    
		if(_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};

void testSmartptr()
{
    
    
	int *p = new int;
	Smartptr<int> sp(p);
}

智能指针的原理

上面讲的RAII的实现方法,还不能算智能指针,因为其少了指针的几个特有的操作

  1. 解引用*
  2. 指向操作 ->

因此只需要将上面的两种操作在RAII类中进行重载就可以。

template<class T>

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 a;
	int b;
};
void testSmartptr()
{
    
    
	Smartptr<int> sp(new int);  //sp相当于一个对象,用来管理资源
	*sp = 10;
	cout << *sp << endl;
	Smartptr<Date> sp2(new Date);
	sp2->a = 1;
	sp2->b = 2;
	cout << sp2->a << sp2->b << endl;
}

上面这种方法在拷贝构造函数与赋值运算符重载方法中不可行,容易产生浅拷贝。但是不能使用深拷贝来解决,因为深拷贝会产生两块空间,同时,资源是外部用户提供的,智能指针没有申请空间的权限,只能对资源进行管理。

  • C++98 中 autoPtr原理

在autoPtr库函数中,实现原理就是在拷贝构造函数与赋值运算符重载中,将后面对象的内容转移到前面对象里面去,最后将后面的对象内容置空。

template<class T>
	class AutoPtr
	{
    
    
	public:
		AutoPtr(T* ptr)
			:_ptr(ptr)
		{
    
    }
		~AutoPtr()
		{
    
    
			if (_ptr)
				delete _ptr;
		}
		AutoPtr(AutoPtr<T>&sp) //拷贝构造函数
			:_ptr(sp._ptr)
		{
    
    
			sp._ptr = nullptr;
		}
		AutoPtr<T>& operator=(AutoPtr<T>&sp)
		{
    
    
			if (*this != sp)	//先判断是否自己给自己赋值
			{
    
    
				if (_ptr)  //如果赋值等号前面的有空间,需将其释放
					delete _ptr;
				_ptr = sp._ptr;
				sp._ptr = nullptr;
			}
			return *this;
		}
		AutoPtr* operator->()
		{
    
    
			return this;
		}
		AutoPtr& operator*()
		{
    
    
			return *this;
		}
	private:
		T* _ptr;
	};

因为发生深拷贝与赋值时,只能使用一份,所以c++标准委员会不建议使用。

auto_ptr的改进版本:
实现原理:增加一个参数,bool owner,用来管理资源释放的权利。

if (_ptr && _owner) //资源存在,同时拥有资源释放的权利
					delete _ptr;
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>& p)
		:_ptr(p._ptr)
		,_owner(p._owner)
	{
    
    
		p._owner = false;
	}
	
	auto_ptr& operator*()
	{
    
    
		return *_ptr;
	}

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

	auto_ptr<T>& operator = (auto_ptr& p)
	{
    
    
		if (this != &p)
		{
    
    
			if (_ptr && _owner)
				delete _ptr;
			_ptr = p._ptr;
			_owner = p._owner;
			p._owner = false;
		}
		return *this;
	}

	~auto_ptr()
	{
    
    
		if (_ptr && _owner)
			delete _ptr;
	}
private: 
	T* _ptr;
	bool _owner;
};

auto_ptr的改性版本可能会造成野指针。

  • unique_ptr库函数,因为智能指针容易产生浅拷贝,所以就禁止拷贝构造函数与赋值运算符的重载,将拷贝构造函数和赋值运算符重载写成私有成员函数。
template<class T>
class  Uniqueptr
{
    
    
public:
	Uniqueptr(T* ptr = nullptr)
		:_ptr(ptr)
	{
    
    }
	~Uniqueptr()
	{
    
    
		if (_ptr)
			delete _ptr;[]
	}
	Uniqueptr* operator->()
	{
    
    
		return this;
	}
	Uniqueptr& operator*()
	{
    
    
		return *this;
	}
	Uniqueptr(Uniqueptr<T>const &) = delete;
	Uniqueptr<T>& operator = (Uniqueptr<T>const &) = delete;
private:
	
	T* _ptr;
};
  • shared_ptr
    就是通过计数方式实现资源的共享。通过计数的方式来判断是否需要进行释放资源,shared_ptr,给每份资源维护了一个计数,用来记录每份资源被几个对象共享,在对象调用析构函数时,计数-1,当计数减到0时,表示最后一份资源不适用了,最后释放该资源。当计数不是0时表示该资源还在使用中,不能释放该资源。

在这里插入图片描述shared_ptr = RAII + operator* +opeartor-> + 计数

#include<mutex> //并发程序互斥锁 
template<class T>
class shared_ptr 
{
    
    
public:
	shared_ptr(T *ptr)
		:_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)
	{
    
    
		addcount();
	}
	shared_ptr<T>& operator=(const shared_ptr<T> &sp)
	{
    
    
		if (_ptr != sp._ptr)
		{
    
    
			Release(); //释放旧空间
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			addcount();			//计数+1
			//_pmutex = sp._pmutex;
		}
		return *this;
	}

	T& operator*()
	{
    
    
		return *this;
	}
	T* operator->()
	{
    
    
		return this;
	}

	void addcount()
	{
    
    
		//_pmutex->lock(); //加锁
		++(*_pcount);			//计数+1
		//_pmutex->unlock();//解锁
	}

	void Release()
	{
    
    
		//bool deleteflag = false;
		if (0 == --(*_pcount))
		{
    
    
			delete _ptr;
			delete _pcount;
		}
		/*if (deleteflag == ture)
		{
			delete _pmutex;
		}*/
	}

	int usecount()
	{
    
    
		return *_pcount;
	}
private:
	T * _ptr;
	int *_pcount;
	//mutex* _pmutex; 多线程加锁
};

shared_ptr改进,由于shared_ptr,所管理的指针有多重申请方式,导致不能将释放函数写成固定的,所以析构函数需要模板特化。

//定制删除器
template<class T>
class DFDel //默认new出来的空间
{
    
    
public:
	void operator()(T*& p)
	{
    
    
		if (p)
		{
    
    
			delete p;
			p = nullptr;
		}
	}
};

template<class T>
class Free //malloc申请的空间
{
    
    
public:
	void operator()(T*& p)
	{
    
    
		if (p)
		{
    
    
			free(p);
			p = nullptr;
		}
	}
};



class Fclose
{
    
    
public:
	void operator()(FILE* p)
	{
    
    
		if (p)
		{
    
    
			fclose(p);
			p = nullptr;
		}
	}
};

namespace bai
{
    
    
	template<class T,class DF = DFDel<T>>  //DF为删除类型,DF默认为new出来的空间
	class shared_ptr
	{
    
    
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
    
    
			if (_ptr)
				_pcount = new int(1);
		}
		shared_ptr(const shared_ptr<T>& p)
			:_ptr(p._ptr)
			, _pcount(p._pcount)
		{
    
    
			if (_ptr) // 如果资源不为nullptr
				++(*_pcount);
		}

		//p1 == p2
		//p1:未管理资源------直接p2共享
		//p1:单独管理资源----在于p2共享之前先释放自己的资源
		//p1:与其他对象共享资源---p1计数--,p2计数++
		shared_ptr<T> operator = (const shared_ptr<T>& p)
		{
    
    
			if (this != &p)
			{
    
    
				if (_ptr && 0 == --(*_pcount))
				{
    
    
					delete _ptr;
					delete _pcount;
				}

				_ptr = p._ptr;
				_pcount = p._pcount;
				if (_ptr)
					++(*_pcount);
			}
			return *this;
		}


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

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


		~shared_ptr()
		{
    
    
			if (_ptr && --(*_pcount) == 0) //由于资源的申请方式不同,
			{
    
    							//所以需要根据资源的类型,定制释放的方式
				//delete _ptr;
				DF()(_ptr);				//DF相当于一种类型,DF()相当于创建一个无名的对象,DF()(_ptr),相当于DF对象调用
				delete _pcount;
			}
		}

		int usecount()
		{
    
    
			return *_pcount;
		}
	private:
		T* _ptr;
		int* _pcount;
	};
}
void TestFunc()
{
    
    
	bai::shared_ptr<int,DFDel<int>> p1(new int);
	bai::shared_ptr<int,DFDel<int>> p2(p1);
	bai::shared_ptr<FILE, Fclose> p3(fopen("666.text" ,"rb")); 
}

shared_ptr的缺陷:
当使用智能指针管理双向链表时,容易产生循环引用。

struct ListNode
{
    
    
	ListNode(int data = int())
	:_pre(nullptr)
	, _next(nullptr)
	{
    
    
		cout << "ListNode()" << this << endl;
	}

	~ListNode()
	{
    
    
		cout << "~ListNode()" << endl;
	}
	shared_ptr<ListNode> _pre;
	shared_ptr<ListNode> _next;
	int data;
};


void TestFunc()
{
    
    
	shared_ptr<ListNode> p1(new ListNode(10));
	shared_ptr<ListNode> p2(new ListNode(20));
	cout << p1.use_count() << endl;//查看p1中的引用计数
	cout << p2.use_count() << endl;
	p1->_next = p2;
	p2->_pre = p1;
	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;
}

在这里插入图片描述
没有调用析构函数,导致资源泄露
在这里插入图片描述解决shared_ptr循环引用
weak_ptr:不能单独管理资源,必须配合shared_ptr一起使用。
weak_ptr:就是为了解决shared_ptr存在的循环引用

struct ListNode
{
    
    
	ListNode(int _data = int())
		:data(_data)
	{
    
    
		cout << "ListNode()" << this << endl;
	}

	~ListNode()
	{
    
    
		cout << "~ListNode()" << endl;
	}
	weak_ptr<ListNode> _pre;  //****
	weak_ptr<ListNode> _next;
	int data;
};

因为weak_ptr不能单独管理资源,所以构造函数不能给_pre,_next初始化
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_42708024/article/details/102532950