1. 程式人生 > >引用計數智能指針

引用計數智能指針

times ostream 減少 編寫 cout 常量 for c++ 靜態鏈表

<a>C++ <span style="font-family:宋體;">智能指針具體解釋</span></a> 

一、簡單介紹

因為 C++ 語言沒有自己主動內存回收機制。程序猿每次 new 出來的內存都要手動 delete

程序猿忘記 delete。流程太復雜。終於導致沒有 delete。異常導致程序過早退出,沒有運行 delete 的情況並不罕見。

用智能指針便能夠有效緩解這類問題,本文主要解說參見的智能指針的使用方法。包含:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr

、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost::intrusive_ptr。你可能會想,如此多的智能指針就為了解決newdelete匹配問題.

以下就依照順序解說如上 7 種智能指針(smart_ptr)。

二、詳細使用

對於編譯器來說,智能指針實際上是一個棧對象。並不是指針類型。在棧對象生命期即將結束時,智能指針通過析構函數釋放有它管理的堆內存。全部智能指針都重載了“operator->”操作符,直接返回對象的引用,用以操作對象。訪問智能指針原來的方法則使用“.”操作符。

訪問智能指針包括的裸指針則能夠用 get()

函數。

因為智能指針是一個對象,所以if (my_smart_object)永遠為真,要推斷智能指針的裸指針是否為空,須要這樣推斷:if (my_smart_object.get())

智能指針包括了 reset() 方法,假設不傳遞參數(或者傳遞 NULL),則智能指針會釋放當前管理的內存。

假設傳遞一個對象,則智能指針會釋放當前對象,來管理新傳入的對象。

我們編寫一個測試類來輔助分析:

std::auto_ptr

std::auto_ptr 屬於 STL。當然在 namespace std 中,包括頭文件 #include<memory> 便可以使用。std::auto_ptr

可以方便的管理單個堆內存對象。

std::auto_ptr 的源代碼後。我們看到。罪魁禍首是“my_memory = my_memory”。這行代碼,my_memory2 全然奪取了 my_memory 的內存管理全部權,導致 my_memory 懸空,最後使用時導致崩潰。

所以,使用 std::auto_ptr 時。絕對不能使用“operator=”操作符。

我們調用 release() 函數釋放內存,結果卻導致內存泄露(在內存受限系統中,假設my_memory占用太多內存。我們會考慮在使用完畢後,立馬歸還,而不是等到 my_memory 結束生命期後才歸還)。

原來 std::auto_ptr release() 函數僅僅是讓出內存全部權。這顯然也不符合 C++ 編程思想。

std::auto_ptr 可用來管理單個對象的對內存,可是。請註意例如以下幾點:

1 盡量不要使用“operator=”。假設使用了,請不要再使用先前對象。

2 記住 release() 函數不會釋放對象。只歸還全部權。

3 std::auto_ptr 最好不要當成參數傳遞(讀者能夠自行寫代碼確定為什麽不能)。

4 因為 std::auto_ptr “operator=”問題,有其管理的對象不能放入 std::vector 等容器中。

5 ……

使用一個 std::auto_ptr 的限制還真多。還不能用來管理堆內存數組,這應該是你眼下在想的事情吧,我也認為限制挺多的,哪天一個不小心。就導致問題了。

因為 std::auto_ptr 引發了諸多問題,一些設計並非很符合 C++ 編程思想

boost::scoped_ptr

boost::scoped_ptr 於boost 庫。定義在 namespace boost 中,包括頭文件 #include<boost/smart_ptr.hpp> 便能夠使用。

boost::scoped_ptr std::auto_ptr 一樣,能夠方便的管理單個堆內存對象,特別的是。boost::scoped_ptr 獨享全部權,避免了 std::auto_ptr 惱人的幾個問題。

boost::scoped_ptr 也能夠像 auto_ptr 一樣正常使用。

但其沒有 release() 函數,不會導致先前的內存泄露問題。

其次。因為 boost::scoped_ptr 是獨享全部權的,所以明白拒絕用戶寫“my_memory2 = my_memory”之類的語句,能夠緩解 std::auto_ptr 幾個惱人的問題。

因為 boost::scoped_ptr 獨享全部權,當我們真真須要復制智能指針時,需求便滿足不了。如此我們再引入一個智能指針,專門用於處理復制,參數傳遞的情況,這便是例如以下的 boost::shared_ptr

boost::shared_ptr

boost::shared_ptr 屬於 boost 庫。定義在 namespace boost 中。包括頭文件 #include<boost/smart_ptr.hpp> 便能夠使用。在上面我們看到 boost::scoped_ptr 獨享全部權。不同意賦值、拷貝。boost::shared_ptr 是專門用於共享全部權的。因為要共享全部權。其在內部使用了引用計數。boost::shared_ptr 也是用於管理單個堆內存對象的。

boost::shared_ptr 也能夠非常方便的使用。而且沒有 release() 函數。關鍵的一點。boost::shared_ptr 內部維護了一個引用計數,由此能夠支持復制、參數傳遞等。boost::shared_ptr 提供了一個函數 use_count() 。此函數返回 boost::shared_ptr 內部的引用計數。

當我們須要使用一個共享對象的時候,boost::shared_ptr 是再好只是的了。

在多線程中使用shared_ptr時,假設存在拷貝或賦值操作,可能會因為同一時候訪問引用計數而導致計數無效。解決方法是向每一個線程中傳遞公共的week_ptr,線程中須要使用shared_ptr時,將week_ptr轉換成shared_ptr就可以。你能夠用下列方法把 shared_ptr 傳遞給還有一個函數:

向 shared_ptr 傳遞值。 調用復制構造函數,遞增引用計數,並把被調用方當做全部者。還有就是在這次操作中有少量的開銷,這非常大程度上取決於你傳遞了多少 shared_ptr 對象。當調用方和被調用方之間的代碼協定 (隱式或顯式) 要求被調用方是全部者,使用此選項。

通過引用或常量引用來傳遞 shared_ptr。 在這樣的情況下,引用計數不添加,而且僅僅要調用方不超出範圍。被調用方就能夠訪問指針。 或者,被調用方能夠決定創建一個基於引用的 shared_ptr。從而成為一個共享全部者。 當調用者並不知道被被調用方。或當您必須傳遞一個 shared_ptr。並希望避免因為性能原因的復制操作,請使用此選項。

通過底層的指針或引用底層的對象。

這使得被調用方使用對象,但不使共享全部權或擴展生存期。

假設被調用方從原始指針創建一個 shared_ptr,則新的 shared_ptr 是獨立於原來的。且沒有控制底層的資源。 當調用方和被調用方之間的協定中明白規定調用者保留shared_ptr 生存期的全部權,則使用此選項。

當您決定怎樣傳遞一個 shared_ptr時。確定被調用方是否有共享基礎資源的全部權。一個“全部者”就是僅僅要它須要就能夠使用底層資源的對象或函數。 假設調用方必須保證被調用方能夠在其(函數)生存期以外擴展指針的生存期。請使用第一個選項。

假設您不關心被調用方是否擴展生存期,則通過引用傳遞並讓被調用方復制它。

假設不得不同意幫助程序函數訪問底層指針,而且您知道幫助程序函數將使用指針且在調用函數返回前先返回,則該函數不必共享底層指針的全部權。不過在調用方的 shared_ptr 的生存期內同意訪問指針。在這樣的情況下,通過引用來傳遞 shared_ptr,通過原始指針或引用的基本對象都是安全的。通過此方式提供一個小的性能改進,而且還有助於表示程序的意圖。

有時,比如在一個 std:vector<shared_ptr<T>>中,您可能必須對傳遞每一個shared_ptr 給lambda表達式體或命名函數對象。 假設lambda或函數沒有存儲指針,則通過引用傳遞shared_ptr。以避免調用拷貝構造函數的每一個元素。

boost::scoped_array

boost::scoped_array 屬於 boost 庫,定義在 namespace boost 中,包括頭文件 #include<boost/smart_ptr.hpp> 便能夠使用。

boost::scoped_array 便是用於管理動態數組的。跟 boost::scoped_ptr 一樣,也是獨享全部權的。

boost::scoped_array<Simple> my_memory(new Simple[2]); // 使用內存數組來初始化boost::scoped_array 的使用跟 boost::scoped_ptr 差點兒相同。不支持復制,而且初始化的時候須要使用動態數組。另外。boost::scoped_array 沒有重載“operator*”,事實上這並無大礙,普通情況下,我們使用 get() 函數更明白些了。

boost::shared_array

一個用引用計數解決復制、參數傳遞的智能指針類。

boost::shared_array 屬於 boost 庫,定義在 namespace boost 中,包括頭文件 #include<boost/smart_ptr.hpp> 便能夠使用。

因為 boost::scoped_array 獨享全部權顯然在非常多情況下(參數傳遞、對象賦值等)不滿足需求,由此我們引入 boost::shared_array。跟 boost::shared_ptr 一樣,內部使用了引用計數。

boost::shared_ptr 一樣。使用了引用計數。能夠復制,通過參數來傳遞。

至此。我們講過的智能指針有 std::auto_ptrboost::scoped_ptrboost::shared_ptrboost::scoped_arrayboost::shared_array。這幾個智能指針已經基本夠我們使用了,90% 的使用過標準智能指針的代碼就這 5 種。可例如以下還有兩種智能指針,它們肯定實用。但有什麽用處呢,一起看看吧。

boost::weak_ptr

boost::weak_ptr 屬於 boost 庫,定義在 namespace boost 中。包括頭文件 #include<boost/smart_ptr.hpp> 便能夠使用。

在講 boost::weak_ptr 之前,讓我們先回想一下前面解說的內容。似乎 boost::scoped_ptrboost::shared_ptr 這兩個智能指針就能夠解決全部單個對象內存的管理了,這兒還多出一個 boost::weak_ptr,是否還有某些情況我們沒納入考慮呢?

回答:有。首先 boost::weak_ptr 是專門為 boost::shared_ptr 而準備的。有時候,我們僅僅關心是否能使用對象,並不關心內部的引用計數。

boost::weak_ptr boost::shared_ptr 的觀察者(Observer)對象。觀察者意味著 boost::weak_ptr 僅僅對 boost::shared_ptr 進行引用。而不改變其引用計數。當被觀察的 boost::shared_ptr 失效後,對應的 boost::weak_ptr 也對應失效。

boost::weak_ptr<Simple> my_memory_weak;

boost::shared_ptr<Simple> my_memory(new Simple(1))

我們看到,雖然被賦值了。內部的引用計數並沒有什麽變化。如今要說的問題是,boost::weak_ptr 究竟有什麽作用呢?從上面那個樣例看來,似乎沒有不論什麽作用,事實上 boost::weak_ptr 主要用在軟件架構設計中能夠在基類(此處的基類並不是抽象基類,而是指繼承於抽象基類的虛基類)中定義一個 boost::weak_ptr用於指向子類的 boost::shared_ptr,這樣基類只觀察自己的 boost::weak_ptr 是否為空就知道子類有沒對自己賦值了。而不用影響子類 boost::shared_ptr 的引用計數,用以減少復雜度,更好的管理對象。

boost::intrusive_ptr

boost::intrusive_ptr屬於 boost 庫,定義在 namespace boost 中。包括頭文件 #include<boost/smart_ptr.hpp> 便能夠使用。

講完如上 6 種智能指針後。對於一般程序來說 C++ 堆內存管理就夠用了,如今有多了一種 boost::intrusive_ptr,這是一種插入式的智能指針,內部不含有引用計數。須要程序猿自己增加引用計數。不然編譯只是。個人感覺這個智能指針沒太大用處,至少我沒用過。有興趣的朋友自己研究一下源碼哦。

自己動手的一個帶引用計數的智能指針:

#include <iostream>
using namespace std;
int const MEM_ALLOC = 100;
class HeapTable
{
public:
	HeapTable()
	{
		mhead = new Node;
	}
	void AddRef(void *ptr)
	{
		Node *p = mhead->mpnext;
		while (p!=NULL)
		{
			if(p->mheapaddr == ptr)
			{
				p->counter++;
				return;
			}
			p = p->mpnext;
		}
		p = new Node(ptr);
		p->mpnext = mhead->mpnext;
		mhead->mpnext = p;
	}
	void DelRef(void *ptr)
	{
		Node *p = mhead->mpnext;
		while (p!=NULL)
		{
			if(p->mheapaddr == ptr)
			{
				p->counter--;
				return;
			}
			p = p->mpnext;
		}
	}
	int GetRef(void *ptr)
	{
		Node *p = mhead->mpnext;
		while (p!=NULL)
		{
			if(p->mheapaddr == ptr)
			{
				return p->counter;
			}
			p = p->mpnext;
		}

		return -1;
	}
private:
	class Node
	{
	public:
		Node(void *ptr = NULL):mheapaddr(ptr),counter(0),mpnext(NULL)
		{
			if(mheapaddr != NULL)//有一個新的節點
			{
				counter = 1;
			}
		}
		static void *operator new(size_t size);
		static void operator delete(void *ptr);
		void *mheapaddr;
		int  counter;
		Node *mpnext;
		static Node*mFreeList;
	};
	
	Node *mhead;
};
HeapTable::Node* HeapTable::Node::mFreeList = NULL;
void *HeapTable::Node::operator new(size_t size)
{
    Node *p = NULL;
	if(mFreeList == NULL)
	{
		int allocsize = size*MEM_ALLOC;
		mFreeList = (Node*)new char[allocsize];
		for(p = mFreeList;p<mFreeList+MEM_ALLOC-1;++p)//靜態鏈表
		{
			 p->mpnext = p+1;
		}
		p->mpnext = NULL;
	}

	p = mFreeList;
	mFreeList=mFreeList->mpnext; //從頭取結點
	return p;
}

void HeapTable::Node::operator delete(void *ptr)
{
	if(ptr == NULL)
	{
		return ;
	}
	Node *p = (Node*)ptr;
	p->mpnext = mFreeList;
	mFreeList = p;
}
template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T *ptr = NULL);
	~CSmartPtr();
	CSmartPtr(const CSmartPtr<T> &src);
	CSmartPtr<T>& operator=(const CSmartPtr<T> &src);
	T& operator*(){return *mptr;}
	void AddRef();
	void DelRef();
	int GetRef();
	const T&operator*()const{return *mptr;}
	const T *operator->()const{return mptr;}
private:
	T *mptr;
	static HeapTable mHeapTable;
};
template<typename T>
HeapTable CSmartPtr<T>::mHeapTable;

template<typename T>
CSmartPtr<T>::CSmartPtr(T *ptr = NULL)
{		
	mptr = ptr;
	if(mptr != NULL)
	{
		AddRef();
	}
}

template<typename T>
CSmartPtr<T>::CSmartPtr(const CSmartPtr<T> &src)
{
	mptr = src.mptr;
	if(mptr!= NULL)
	{
		AddRef();
	}
}

template<typename T>
CSmartPtr<T>& CSmartPtr<T>::operator= (const CSmartPtr<T> &src)
{
	if(this == &src)
	{
		return *this;
	}
	DelRef();//減去引用計數
	if(GetRef() == 0)
	{
		delete mptr;
		mptr = NULL;
	}
	AddRef();
	mptr = src.mptr;

	return *this;
}

template<typename T>
CSmartPtr<T>::~CSmartPtr()
{
	DelRef();
	if(GetRef() == 0)
	{
		delete mptr;
	}
}
template<typename T>
void CSmartPtr<T>::AddRef()
{
	mHeapTable.AddRef(mptr);
}
template<typename T>
void CSmartPtr<T>::DelRef()
{
	mHeapTable.DelRef(mptr);
}
template <typename T>
int CSmartPtr<T>::GetRef()
{
	return mHeapTable.GetRef(mptr);
}

class Text
{
public:
	Text(){cout<<"construction call!"<<endl;}
	~Text(){cout<<"destruction call !"<<endl;}
private:
	int ma;
	int mb;
};
class CHello
{
public:
	CHello(){cout<<"construction call! hello "<<endl;}
	~CHello(){cout<<"destruction call!  byebye"<<endl;}
private:
	int ma;
	int mb;
};
int main ()
{
	CSmartPtr<Text> mptr(new Text);
	CSmartPtr<Text> mptr2;
	mptr2 = mptr;
	CSmartPtr<CHello> mptr1(new CHello);
	return 0;
}



總結

如上講了這麽多智能指針,有必要對這些智能指針做個總結:

1、在能夠使用 boost 庫的場合下,拒絕使用 std::auto_ptr,由於其不僅不符合 C++ 編程思想。並且極easy出錯[2]

2、在確定對象無需共享的情況下。使用 boost::scoped_ptr(當然動態數組使用 boost::scoped_array)。

3、在對象須要共享的情況下,使用 boost::shared_ptr(當然動態數組使用 boost::shared_array)。

4、在須要訪問 boost::shared_ptr 對象。而又不想改變其引用計數的情況下,使用 boost::weak_ptr一般經常使用於軟件框架設計中

5、最後一點,也是要求最苛刻一點:在你的代碼中,不要出現 delete keyword(或 C 語言的 free 函數)。由於能夠用智能指針去管理。

引用計數智能指針