C++智能指针的一些要点

524 阅读3分钟

为什么引入智能指针

我们知道当我们定义一个普通指针变量的时候,我们在最后的时候,要把这个指针给销毁,以防资源泄露。 但是当创建指针和销毁指针期间如果发生了异常,这时就会发生资源泄露,由此智能指针应运而生,它可以自动销毁,避免了资源泄露的问题

auto_ptr

#include<iostream>

using namespace std;

namespace zy {

template<class T>

class auto_ptr

{

public:

auto_ptr(T* ptr = NULL)

:_ptr(ptr)

{

\


}

auto_ptr(auto_ptr<T>& ap)

:_ptr(ap._ptr)

{

\


}

auto_ptr<T>& operator=(auto_ptr<T>& ap)

{

if (ap != &this)

\


{

if (_ptr)

delete _ptr;

_ptr = ap._ptr;

ap._ptr = NULL;

}

return *this;

}

~auto_ptr()

{

if (_ptr)

delete _ptr;

}

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

T* operator->(){return _ptr;

}

private:

T* _ptr;

\


};

}

class Date

{

public:

Date() { cout << "Date()" << endl; }

~Date() { cout << "~Date()" << endl; }

int _year;

int _month;

int _day;

};

int main()

{

zy::auto_ptr<Date> ap1(new Date);

zy::auto_ptr<Date> ap2(ap1);

ap1->_day = 0;

\


\


}

缺点:这个智能指针当A指针赋值给另一个B指针的时候,A指针会发生悬空,导致A指针无法正常使用

image.png

unque_ptr


template<class T>
	class unique_ptr
	{
		unique_ptr(T* ptr = NULL)
			:_ptr(ptr)
		{

		}
		unique_ptr(unique_ptr<T>& ap)
			:_ptr(ap._ptr)
		{

		}
		unique_ptr<T>& operator=(unique_ptr<T>& ap)
		{
			if (ap != &this)

			{
				if (_ptr)
					delete _ptr;
				_ptr = ap._ptr;
				ap._ptr = NULL;
			}
			return *this;
		}
		~unique_ptr()
		{
			if (_ptr)
				delete _ptr;
		}
		T& operator*() { return  *_ptr; }
		T* operator->() {
			return _ptr;
		}
	private:
		unique_ptr(unique_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
		}
		unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
		
		T* _ptr;
                };




缺点:这个智能指针会简单粗暴的直接不让拷贝构造和赋值,所以多个对象无法共享同一份资源

shared_ptr

struct Date
{
	int _year = 0;
	int _month = 0;
	int _day = 0;
};

namespace wzy {
	
	template<class T>
	class shared_ptr
	{
	public:
		
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _pRefCount(new int(1))
			,_pmtx(new mutex)

		{

		}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pRefCount(sp._pRefCount)
			,_pmtx(sp._pmtx)

		{
			++(*_pRefCount);
		}
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (this->_ptr != sp._ptr)
			{
				Release();
				_ptr = sp._ptr;
				_pRefCount = sp._pRefCount;
				_pmtx = sp._pmtx;
				AddRef();

			}
			
			return *this;

		}
		~shared_ptr()
		{
			if (--(*_pRefCount) == 0 && _ptr)
			{
				cout << "delete" << _ptr << endl;
				delete _ptr;
				delete _pRefCount;
				_ptr = nullptr;
				_pRefCount = nullptr;
			}
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		void Release()
		{
			_pmtx->lock();
			bool flag = false;
				if (--(*_pRefCount) == 0)
				{
					delete _ptr;
					delete _pRefCount;
					flag = true;
				}
				_pmtx->unlock();
				if(flag)
				delete _pmtx;
				
		}
		void AddRef()
		{
			_pmtx->lock();
			++(*_pRefCount);
			_pmtx->unlock();
		}
		int useCount()
		{
			return *_pRefCount;
		}

	private:
		T* _ptr;
		//static int _refCount = 0;
		int* _pRefCount;
		mutex* _pmtx;//只有用指针才能
	};
	
}

shared_ptr采用了引用计数的方式,通过计数来记录构造的次数,

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。

  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。

  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;

  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。进而完成合理的析构,但是缺点是会产生循环引用

std::shared_ptr<ListNode> n1(new ListNode);
	std::shared_ptr<ListNode> n2(new ListNode);
	n1->_next = n2;
	n2->_prev = n1;

上面这个代码就会产生循环引用的问题,这两个指针的析构会互相依赖,n2析构要依赖n1,n1析构要依赖n2,这样就产生了一个死循环了,就会发生错误。这样我们就引入了weak_ptr

weak_ptr

我常常把weak_ptr当作shared_ptr的小弟,我们知道shared_ptr不仅可以访问指向的内存区域而且还可以掌管指向区域的“生死大权”,也就是说伴随着shared_ptr指针生命周期的结束,指针指向的区域也会被释放掉。但是weak_ptr作为shared_ptr的小弟,weak_ptr不敢这样做,作为智能指针中最弱的一个,weak_ptr只能访问所指向的内存区域,当weak_ptr指针生命结束之时,其所指向的内存依旧完好无损,这就是weak_ptr小弟和shared_ptr大哥的本质区别。

由于在weak_ptr指针生命结束之时,不会对指向内存产生任何影响,因此不会出现“上述shared_ptr引发的环形引用的异常错误”。如果将上述例子中,shared_ptr换做weak_ptr结构将会发生变化:

image.png 也就是说,这里的shared_ptr变成weak_ptr就会解决相应的问题了。