智能指針

     智能指針

  •   概念
    所謂智能指針就是智能/自動的管理指針所指動態資源的釋放。 現階段的智能指針是一種通用實現技術,是使用引用技術。 

  • 發展歷史
  •    第一階段(C++98):auto_ptr ----自動指針
         auto_ptr 的主要思想是 管理權轉移, 但其缺陷很大,當通過拷貝構造函數,通過操作符=賦值後,原來的那個智能指針對象就失效了。只有新的智能 指針對象可以有效使用了。
  •    第二階段(C++03):即在Boost中的智能指針
         scoped_ptr —— 守衛指針  scoped_ptr的主要思想是 防拷貝
         share_ptr —— 共享指針       share_ptr的主要思想是 引用計數複雜    缺陷:循環引用 
         weak_ptr —— 弱智者,不能單獨存在,輔助解決shared_ptr的循環引用問題
  •    第三階段(C++11):
         unique_ptr    還是運用防拷貝的思想 
         share_ptr   
         weak_ptr----弱指針,不能單獨存在,輔助解決shared_ptr的循環引用問題

  實現
  • AutoPtr
template<class T>
class AutoPtr
{
public:

	AutoPtr(T* ptr)
		:_ptr(ptr)
	{}

	~AutoPtr()
	{
		cout<<"指針被釋放"<<endl;
		if(_ptr)
		{
			delete _ptr;
		}
	}

	//運算符的重載(像指針一樣)
	T& operator* ()
	{
		return *_ptr;//*(this->_ptr)
	}

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

	//拷貝構造
	AutoPtr(AutoPtr<T>& ap)//管理權轉移
		:_ptr(ap._ptr)
	{
		ap._ptr = NULL;
	}

	//ap1=ap2
	AutoPtr<T>& operator=(AutoPtr<T> &ap) 
	{
		if(this != &ap)
		{
			if(_ptr)
				delete _ptr;
			_ptr = ap._ptr;
			ap._ptr = NULL;
		}
		return *this;
	}

private:
	T* _ptr;
};
    分析:這裏是因爲 ap2 完全的奪取了 ap1 的管理權。然後導致訪問 ap1 的時候程序就會崩潰。如果在這裏調用 ap2 = ap1 程序一樣會崩潰,原因還是 ap1 被奪走管理權,所以這種編程思想及其不符合C++思想,所以它的設計思想就是有一定的缺陷。因此,一般不推薦使用 auto_ptr 智能指針
  •     ScopedPtr
template<class T>
class ScopedPtr
{
public:
	ScopedPtr(T* ptr)
		:_ptr(ptr)
	{}

	~ScopedPtr()
	{
		if(_ptr)
		{
			delete _ptr;
		}
	}

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

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

private:
	ScopedPtr(const ScopedPtr<T> &sp);//不能定義爲公有的,防止在類外實現
	ScopedPtr<T>& operator=(const ScopedPtr<T> &sp);

private:
	T *_ptr;
};

注: ScopedPtr將拷貝構造和賦值運算符的重載定義爲protected,並且只聲明不實現,這樣就實現了 防拷貝 的原理。
  • SharedPtr
template<class T>
class SharedPtr
{
	friend class WeakPtr<T>;
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		,_refcount(new int(1))
	{}

	~SharedPtr()//當引用技術爲0時,釋放空間
	{
		cout<<"~SharedPtr()"<<endl;
		if(--(*_refcount)==0)
		{
			printf("ox%p\n",_ptr);
			delete _ptr;
			delete _refcount;
		}
	}

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

	//sp2(sp1)
	SharedPtr(const SharedPtr<T>& sp)
		:_ptr(sp._ptr)
		,_refcount(sp._refcount)
	{
		(*_refcount)++;
	}

	//sp1 = sp2
	SharedPtr<T> &operator = (const SharedPtr<T>& sp)
	{
		if(_ptr != sp._ptr) //防止自已給自已賦值
		{
			if(--(*_refcount)==0)
			{
				delete _ptr;
				delete _refcount;
			}
			_ptr = sp._ptr;
			_refcount = sp._refcount ;
			(*_refcount)++;
		}
		return *this;
	}

private:
	T* _ptr;
	int* _refcount;//引用次數

};

注:share_ptr 因爲它採用引用計數的思想,所有它是功能較爲完善的,但是 share_ptr 還是有缺陷的,例如在解決 循環引用 問題上。
eg: 雙向鏈表
  (1) 無智能指針的雙向鏈表
      struct ListNode
{ 
	int _data;
	ListNode* _next;
	ListNode* _prev;

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


void TestRec()
{	
	ListNode* cur = new ListNode;
	ListNode* next = new ListNode;
	cur->_next = next; 
	next->_prev = cur;
	delete cur;
	delete next;	
}


(2)含有智能指針的雙向鏈表

struct ListNode
{ 
	int _data;
	SharedPtr<ListNode> _next;
	SharedPtr<ListNode> _prev;

	ListNode()
		:_data(0)
		,_next(NULL)
		,_prev(NULL)
	{}

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


void TestRec()
{	
	SharedPtr<ListNode> cur(new ListNode);
	SharedPtr<ListNode> next(new ListNode);

	cur->_next = next; //加上這兩句導致循環引用
	next->_prev = cur;
}
分析: 由運行結果知,智能指針cur、next並沒有釋放,若加cur->_next = next; next->_prev = cur; 這兩句運行結果如下:

由結果可知,由於上面兩句導致循環引用。

畫圖分析如下:

_next、_prev的生命週期依賴於結點的釋放,而cur、next結點又依賴於_next、_prev的釋放,所以導致循環引用。


解決方法:
使用弱引用的智能指針打破這種循環引用。

  •   WeakPtr
  weak_ptr被設計爲與shared_ptr共同工作,可以從一個shared_ptr或者另一個weak_ptr對象構造,獲得資源的觀測權。但weak_ptr沒有共享資源,它的構造不會引起指針引用計數的增加。
template <class T>
class WeakPtr
{
public:
	WeakPtr()
		:_ptr(NULL)
	{}

	WeakPtr(const SharedPtr<T> & sp)
		:_ptr(sp._ptr)
	{}

	WeakPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		_ptr = sp._ptr;
		return *this;
	}

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

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

private:
	T* _ptr;
};

struct ListNode
{ 
	int _data;
	WeakPtr <ListNode> _next;
	WeakPtr <ListNode> _prev;

	ListNode()
		:_data(0)
		,_next(NULL)
		,_prev(NULL)
	{}

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


void TestRec()
{	
	SharedPtr<ListNode> cur(new ListNode);
	SharedPtr<ListNode> next(new ListNode);

	cur->_next = next; 
	next->_prev = cur;

}
由於弱引用不更改引用計數,類似普通指針,只要把循環引用的一方使用弱引用,即可解除循環引用。
注:雖然通過弱引用指針可以有效的解除循環引用,但這種方式必須在程序員能預見會出現循環引用的情況下才能使用,也可以是說這個僅僅是一種編譯期的解決方案,如果程序在運行過程中出現了循環引用,還是會造成內存泄漏的。






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