1. 程式人生 > >智慧指標的分析與實現

智慧指標的分析與實現

一,為什麼要用智慧指標

在編寫c++程式的時候,讓我們最頭痛的問題就是記憶體洩露,也就是說

int* pt = new int;

delete pt;

必須保證new和delete必須成對出現。作為程式猿,可以像使用普通變數一樣來使用指標,這個指標可以在恰當的時候被自動釋放,智慧指標就是這樣一個指標,它的任務是保證每一個被動態分配的記憶體都能夠被釋放。

看一個例子


如果在DealProcessAdoption有一個異常,會發生什麼事情。

所以這段程式碼很危險,當DealProcessAdoption有一個exception,後面的delete程式碼就會跳過,從而造成記憶體洩露。

有兩種解決方案

一種是用try catch,另外一種就是用智慧指標。


二,一個智慧指標的實際例子

class intptr 

private: 
 int* m_p; 
public: 
 intptr(int* p){ m_p = p; } 
 ~intptr(){ delete m_p; } 
 int& operator*(){ return *m_p; } 
};

我們可以方便的執行以下程式碼,而不必擔心記憶體洩漏的問題:
somefunction()
{
 intptr pi(new int); 
 *pi = 10; 
 int a = *pi; 
}

以上我們給出的智慧指標有個致命錯誤。設想我們執行以下程式碼會有怎樣的情況發生:
void somefunction()
{
 intptr pt1(new int);
 intptr pt2(new int);
 *pt1 = 10;
 pt2 = pt1;
}

對於普通指標來說,pt2 = pt1只是讓pt2指向與pt1相同的地址,但是對於我們的智慧指標來說,pt2原先指向的地址被洩露掉了,而pt1所指向的地址被釋放了兩次。所以,我們給每個new出來的記憶體地址對應的分配一個“被指向計數器”,由它記錄這塊記憶體地址被多少指標所指向。

三,boost庫中的智慧指標

 但是這個auto_ptr有很多缺點

1、auto_ptr不能共享所有權。
2、auto_ptr不能指向陣列
3、auto_ptr不能作為容器的成員。
4、不能通過賦值操作來初始化auto_ptr
std::auto_ptr<int> p(newint(42));     //OK
std::auto_ptr<int> p = newint(42);    //ERROR
這是因為auto_ptr 的建構函式被定義為了explicit,不能隱式呼叫
5、不要把auto_ptr放入容器

 

boost庫的share_ptr


shared_ptr是Boost庫所提供的一個智慧指標的實現,shared_ptr就是為了解決auto_ptr在物件所有權上的侷限性(auto_ptr是獨佔的),在使用引用計數的機制上提供了可以共享所有權的智慧指標.
2. shared_ptr比auto_ptr更安全
3. shared_ptr是可以拷貝和賦值的,拷貝行為也是等價的,並且可以被比較,這意味這它可被放入標準庫的一般容器(vector,list)和關聯容器中(map)。

 

四,智慧指標的兩種實現方式

方案一(引入輔助類)


方案二(控制代碼類)

測試:


智慧指標的另外一種標準實現

namespace smart
{
	// 引用計數類.
	class smart_count
	{
	public:
		smart_count(int c = 0) : use_count(c) {}
		~smart_count() {}

		// 增加引用計數, 並返回計數值.
		int addref() { return ++use_count; }
		// 減少引用計數, 並返回計數值.
		int release() { return --use_count; }
	private:
		// 計數變數.
		int use_count;
	};
	// 智慧指標.
	template <class T>
	class smart_ptr
	{
	public:
		// 構造指標, 並使引用計數置為1.用explicit是防止隱式轉換
		explicit smart_ptr (T* ptr) : p(ptr), u(new smart_count(1))
		{}
		// 構造空指標.
		explicit smart_ptr () : p(NULL), u(NULL)
		{}
		// 智慧指標析構.
		~smart_ptr (void)
		{
			// 如果引用計數等於0, 則刪除資料和引用計數, 並置p為NULL.
			// 此處需要注意的是, 共用的u並未置為 NULL, 在其它指標析構
			// 時, p為NULL, 則不會重複delete.
			if (p && u->release() <= 0)
			{
				delete p;
				delete u;
				p = NULL;
			}  
		}
		// 智慧指標拷貝建構函式.
		smart_ptr (const smart_ptr<T>& t)
		{
			p = t.p;
			u = t.u;

			if (u) // 必須判斷空值.
			{
				u->addref(); // 增加引用計數.
			}
		}
		// 指標賦值.
		void operator= (smart_ptr<T>& t)
		{
			// 首先將引用計數減1, 然後再判斷是否小於0, 如果小於0, 則delete.   
			if (p && u->release() <= 0)
			{
				delete p;
				delete u;
			}

			// 直接賦值.
			p = t.p;
			u = t.u;

			if (u) // 必須判斷空值.
			{
				u->addref(); // 增加引用計數.
			}
		}
		// 過載->操作和*操作符.
		T *operator-> (void) { return p; }
		T& operator *(void) { return *p; }
		// 過載!操作符.
		bool operator! () const { return !p;}

		// 過載指標bool值操作符.
		typedef smart_ptr<T> this_type;
		typedef T * this_type::*unspecified_bool_type;
		operator unspecified_bool_type() const { return !p ? 0: &this_type::p; }
		// 得到原指標.
		T* get() { return p; }
		void reset(T* ptr)
		{
			// 首先將引用計數減1, 然後再判斷是否小於0, 如果小於0, 則delete.   
			if (p && u->release() <= 0)
			{
				delete p;
				delete u;
			}
			// 賦值, 如果是NULL, 則不建立引用計數.
			p = ptr;
			if (p)
				u = new smart_count(1);
			else
				u = NULL;
		}
		void reset(smart_ptr<T>& t)
		{
			// 首先將引用計數減1, 然後再判斷是否小於0, 如果小於0, 則delete.   
			if (p && u->release() <= 0)
			{
				delete p;
				delete u;
			}

			// 賦值.
			p = t.p;
			u = t.u;

			if (u) // 必須判斷空值.
			{
				u->addref(); // 增加引用計數.
			}
		}
	private:
		T* p;
		smart_count* u;
	};
	// 過載==操作符.
	template<class T, class U> inline bool operator==(smart_ptr<T> & a, smart_ptr<U> & b)
	{
		return a.get() == b.get();
	}
	// 過載!=操作符.
	template<class T, class U> inline bool operator!=(smart_ptr<T> & a, smart_ptr<U> & b)
	{
		return a.get() != b.get();
	}
}