程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 引用計數智能指針

引用計數智能指針

編輯:C++入門知識

引用計數智能指針


一、簡介

由於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。你可能會想,如此多的智能指針就為了解決new、delete匹配問題.

下面就按照順序講解如上7種智能指針(smart_ptr)。

二、具體使用

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

訪問智能指針包含的裸指針則可以用get()函數。由於智能指針是一個對象,所以if(my_smart_object)永遠為真,要判斷智能指針的裸指針是否為空,需要這樣判斷:if(my_smart_object.get())。

智能指針包含了reset()方法,如果不傳遞參數(或者傳遞NULL),則智能指針會釋放當前管理的內存。如果傳遞一個對象,則智能指針會釋放當前對象,來管理新傳入的對象。

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

std::auto_ptr

std::auto_ptr屬於STL,當然在namespacestd中,包含頭文件#include便可以使用。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庫,定義在namespaceboost中,包含頭文件#include便可以使用。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庫,定義在namespaceboost中,包含頭文件#include便可以使用。在上面我們看到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給lambda表達式體或命名函數對象。如果lambda或函數沒有存儲指針,則通過引用傳遞shared_ptr,以避免調用拷貝構造函數的每個元素。

 

boost::scoped_array

boost::scoped_array屬於boost庫,定義在namespaceboost中,包含頭文件#include便可以使用。

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

boost::scoped_arraymy_memory(newSimple[2]); //使用內存數組來初始化boost::scoped_array的使用跟boost::scoped_ptr差不多,不支持復制,並且初始化的時候需要使用動態數組。另外,boost::scoped_array沒有重載“operator*”,其實這並無大礙,一般情況下,我們使用get()函數更明確些了。

boost::shared_array

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

boost::shared_array屬於boost庫,定義在namespaceboost中,包含頭文件#include便可以使用。

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

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

至此,我們講過的智能指針有std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array。這幾個智能指針已經基本夠我們使用了,90%的使用過標准智能指針的代碼就這5種。可如下還有兩種智能指針,它們肯定有用,但有什麼用處呢,一起看看吧。

 

boost::weak_ptr

boost::weak_ptr屬於boost庫,定義在namespaceboost中,包含頭文件#include便可以使用。

在講boost::weak_ptr之前,讓我們先回顧一下前面講解的內容。似乎boost::scoped_ptr、boost::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_ptrmy_memory_weak;

boost::shared_ptrmy_memory(newSimple(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庫,定義在namespaceboost中,包含頭文件#include便可以使用。

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

 

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

#include 
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;pmpnext = 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
class CSmartPtr
{
public:
	CSmartPtr(T *ptr = NULL);
	~CSmartPtr();
	CSmartPtr(const CSmartPtr &src);
	CSmartPtr& operator=(const CSmartPtr &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
HeapTable CSmartPtr::mHeapTable;

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

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

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

	return *this;
}

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

class Text
{
public:
	Text(){cout<<"construction call!"< mptr(new Text);
	CSmartPtr mptr2;
	mptr2 = mptr;
	CSmartPtr mptr1(new CHello);
	return 0;
}


 

 

總結

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

1、在可以使用boost庫的場合下,拒絕使用std::auto_ptr,因為其不僅不符合C++編程思想,而且極容易出錯[2]。

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

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

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

5、最後一點,也是要求最苛刻一點:在你的代碼中,不要出現delete關鍵字(或C語言的free函數),因為可以用智能指針去管理。

 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved