四種智能指針的用法以及實現原理

先來說一下四種常用的智能指針,我按使用度從低到高排一下順序,分別是auto_ptr, unique_ptr, shared_ptr, weak_ptr,先來列舉一下啊,剩下的我在一個一個慢慢說呀
首先來說一下智能指針的實現原理主要是通過對象生命週期來控制程序資源的簡單技術,然後了既然是指針就是可以進行和指針類似的行爲(解引用和空間內容的訪問

  • 先來看一下auto_ptrd的實現原理吧
template<class T>
class AutoPtr
{
public:
	AutoPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}

	// 拷貝構造:將ap的資源轉移到當前對象上
	AutoPtr(AutoPtr<T>& ap)
		: _ptr(ap._ptr)
	{
		ap._ptr = nullptr;
	}

	AutoPtr<T>& operator=(AutoPtr<T>& ap)
	{
		if (this != &ap)
		{
			if (_ptr)
				delete _ptr;

			_ptr = ap._ptr;
			ap._ptr = nullptr;
		}

		return *this;
	}

	~AutoPtr()
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}

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

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

	T* Get()
	{
		return _ptr;
	}

	void ReSet(T* ptr)
	{
		if (_ptr)
			delete _ptr;

		_ptr = ptr;
	}
protected:
	T* _ptr;
};


struct A
{
	int a;
	int b;
	int c;
};

void TestAutoPtr1()
{
	AutoPtr<int> ap1(new int);
	*ap1 = 10;

	AutoPtr<A> ap2(new A);
	ap2->a = 1;
	ap2->b = 2;
	ap2->c = 3;
}

最顯著的特點就是一個對象的空間只能一個對象用,不可以兩個對象共用同一塊空間,避免了程序崩潰問題,當我們賦值以後我們以前的對象資源就被置空了。
我們使用智能指針的時候我們必須加#include,然後了賦值以後我們以前的對象空間就不能訪問了哦。


class Date {
	public:
		Date() { 
			cout << "Date()" << endl;
		}  
		~Date(){ 
			cout << "~Date()" << endl;
		}

			   int _year;  
			   int _month; 
			   int _day;
	};

int main() {
	auto_ptr<Date> ap(new Date);
	auto_ptr<Date> copy(ap);
	return 0;
}
  • unique的實現原理
#include<istream>
using namespace std;

template<class T>
class UniquePtr
{
public:
	UniquePtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}

	~UniquePtr()
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}

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

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

	T* Get()
	{
		return _ptr;
	}

	
	 private:
	 	UniquePtr(const UniquePtr<T>&);
	 	UniquePtr<T>& operator=(const UniquePtr<T>&);


protected:
	T* _ptr;
};

void TestUniquePtr()
{
	UniquePtr<int> up1(new int);
	UniquePtr<int> up2(new int);
}
int main()
{
	TestUniquePtr();
	return 0;

}

unique_ptr主要的特點是我們不能進行賦值,拷貝,而我們實現也和auot_ptr簡單的實現原理差不多的,主要是拷貝,賦值函數的私有化,並且在c++98裏面我們只聲明不定義。c++11裏面有新的語法這個應該百度一下就知道了
如果我們試圖對unique_ptr進行賦值拷貝時候,就會出現程序崩潰。

unique_ptr的簡單的使用

unique_ptr<int> up1(new int);
unique_ptr<int> up2(new int);
  • 在來看一下shared_ptr吧(weak_ptr是差不多自己感覺是填補shared_ptr的某些方面的漏洞而涉及的這個可以放後面說的)

先來簡單的說一下shared_ptr吧,上面兩個指針也許相比shared_ptr使用度並不是很高,相比之下最簡單的話說shared_ptr是更加智能的智能指針,shared_ptr是通過引用計數的方法管理同一塊內存的,這樣內存什麼時候釋放,內存指向會不會成爲野指針就知道了。

  • shared_ptr的實現原理
template<class T>
class SharedPtr
{
public:
	SharedPtr(T* ptr = nullptr)
		: _ptr(ptr)
		, _pCount(nullptr)
	{
		if (_ptr)
		{
			_pCount = new int(1);
		}
	}

	~SharedPtr()
	{
		if (_ptr && 0 == --(*_pCount))
		{
			delete _ptr;
			delete _pCount;
			_ptr = nullptr;
			_pCount = nullptr;
		}
	}

	SharedPtr(const SharedPtr<T>& sp)
		: _ptr(sp._ptr)
		, _pCount(sp._pCount)
	{
		if (_ptr)
			++(*_pCount);
	}

	
	SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		if (this != &sp)
		{
			if (_ptr && 0 == --(*_pCount))
			{
				delete _ptr;
				delete _pCount;
			}

			_ptr = sp._ptr;
			_pCount = sp._pCount;

			if (sp._ptr)
				++(*_pCount);
		}

		return *this;
	}

	int UseCount()
	{
		return *_pCount;
	}

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

	T* operator->()
	{
		return _ptr;
	}
protected:
	T* _ptr;
	int* _pCount;
};

void TestSharedPtr1()
{
	SharedPtr<int> sp1(new int);
	cout << sp1.UseCount() << endl;

	SharedPtr<int> sp2(sp1);
	cout << sp1.UseCount() << endl;
	cout << sp2.UseCount() << endl;
}

void TestSharedPtr2()
{
	SharedPtr<int> sp1(new int);
	SharedPtr<int> sp2(sp1);

	SharedPtr<int> sp3(new int);


	sp3 = sp1;
}

int main()
{
	TestSharedPtr1();
	TestSharedPtr2();
	return 0;
}

shared_ptr中是存在線程安全問題的,簡單的來說當,B,C對象同時來引用A對象時候也許引用計數並不會加到3,而是2.所以考慮到線程安全問題時候,我們就要加鎖

  • 加鎖以後
#include <mutex>

template<class T, class DF = DefDF<T>>
class SharedPtr
{
public:
	SharedPtr(T* ptr = nullptr)
		: _ptr(ptr)
		, _pCount(nullptr)
		, _pMutex(nullptr)
	{
		if (_ptr)
		{
			_pCount = new int(1);
			_pMutex = new mutex;
		}
	}

	~SharedPtr()
	{
		Release();
	}

	SharedPtr(const SharedPtr<T>& sp)
		: _ptr(sp._ptr)
		, _pCount(sp._pCount)
		, _pMutex(sp._pMutex)
	{
		if (_ptr)
			IncreaseRefCount();
			//++(*_pCount);
	}

	// s1、s2
	// s1 = s2;
	SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		if (this != &sp)
		{
			Release();

			_ptr = sp._ptr;
			_pCount = sp._pCount;

			if (sp._ptr)
				IncreaseRefCount();
				//++(*_pCount);
		}

		return *this;
	}

	int UseCount()
	{
		return *_pCount;
	}

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

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

	void IncreaseRefCount()
	{
		_pMutex->lock();
		++(*_pCount);
		_pMutex->unlock();
	}

	int DecreaseRefCount()
	{
		_pMutex->lock();
		--(*_pCount);
		_pMutex->unlock();

		return *_pCount;
	}

	void Release()
	{
		if (_ptr && 0 == DecreaseRefCount())
		{
			DF()(_ptr);
			delete _pCount;
			delete _pMutex;

			_ptr = nullptr;
			_pCount = nullptr;
			_pMutex = nullptr;
		}
	}

protected:
	T* _ptr;
	int* _pCount;
	mutex* _pMutex;
};

class Date
{
public:
	int _year = 0;
	int _month = 0;
	int _day = 0;
};

void TestSharedThread(SharedPtr<Date>& sp, int n)
{
	for (size_t i = 0; i < n; ++i)
	{
		SharedPtr<Date> copySp(sp);
		sp->_year++;
		sp->_month++;
		sp->_day++;
	}
}

但是在特定環境當中shared_ptr的引用計數未必也是湊效的,例如雙向鏈表的循環引用,這個時候我們必須在雙向節點內部在添加一個引用計數的指針用來通知釋放內部節點,就需要一個weak_ptr
在這裏插入圖片描述

  • 修改以後的雙向鏈表的引用計數
struct ListNode {
	int _data; 
	weak_ptr<ListNode> _prev; 
	weak_ptr<ListNode> _next;

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

int main() {
	shared_ptr<ListNode> node1(new ListNode); 
	shared_ptr<ListNode> node2(new ListNode); 
	cout << node1.use_count() << endl;    
	cout << node2.use_count() << endl;

	node1->_next = node2; 
	node2->_prev = node1;

	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;

	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章