C++智能指針:shared_ptr,uniqe_ptr,weak_ptr

動態內存

在C++中,動態內存的管理是通過一對運算符來完成的:newdelete
new:在動態內存中爲對象分配空間,並返回一個指向該對象的指針
delete:接受一個動態對象的指針,銷燬該對象,並釋放與之關聯的內存

動態內存的使用需要十分小心,因爲要在編程的時候要確保在正確的時間對內存進行釋放是極其困難的。如果釋放內存不及時,在這種情況下就會出現內存泄漏;但若過早的釋放(在仍有指針指向該內存的時候就把其釋放了),就會產生引用非法內存的指針(稱爲空懸指針)。

程序使用動態內存主要處以以下三種原因之一:

  • 程序不知道自己需要多少對象
  • 程序不知道所需對象的準確類型
  • 程序要在多個對象間共享數據(多個對象共享相同底層數據

在C++中,爲了能更安全更容易的使用動態內存,新的標準庫提供了兩種智能指針(smart pointer)類型來管理動態對象(注意:智能指針是類型)。

智能指針

智能指針的行爲類似常規指針,但與常規不同的是,它負責自動釋放所指向對象
新標準庫提供的這兩種智能指針的區別在於管理底層指針的方式:
shared_ptr:允許多個指針指向同一個對象
imoqie_ptr:“獨佔”所指向的對象
同時,標準庫還提供了一個名爲weak_ptr的伴隨類:
weak_ptr:它是一種弱引用,指向shared_ptr所管理的對象。

以上三種類型都定義在memory頭文件中。

shared_ptr類

shared_ptr類類似vector,智能指針也是一種模板(template)。因此在創建智能指針對象的時候,也必須提供指針指向的類型的信息。

shared_ptr<int> p1;
shared_ptr<string> p2;
shared_ptr<int> p3 (new int(16)); //正確
shared_ptr<int> p4 = new int(16);  //錯誤

對於p3,p4,shared_ptr的構造函數是explicit的,因此我們不能將一個內置指針隱式轉換爲一個智能指針(p4的初始化隱式地要求編譯器用一個new返回的int*來創建一個shared_ptr,因此是錯誤的)。

shared_ptr和unique_ptr都支持的操作
shared_ptr< T >sp 空智能指針,默認初始化的智能指針中保存着一個空指針
p 將p作爲一個條件判斷,若p指向一個對象,則爲true
*p 解引用p,獲得p指向的對象
p->mem 等價於(*p).mem
p.get() 返回p中保存的指針。小心使用,若智能指針釋放了其對象,返回的指針所指向的對象也同時消失了。
swap(p,q) 交換p和q的指針
p.swap(q) 交換p和q的指針
shared_ptr獨有的操作
make_shared< T >(args) 返回一個shared_ptr,指向一個動態分配的類型爲T的對象,使用args初始化該對象
shared_ptr< T >p(q) p是shared_ptr q的拷貝,此操作會遞增q中的計數器。q指針必須能轉換爲T*
p = q p和都爲shared_ptr,保存的指針必須能相互轉換,此操作會遞減p的引用計數,遞增q的引用計數;若p的引用計數爲0,則將其管理的內存釋放
p.unique() 若p.use_count()爲1,返回true,否則false
p.use_count() 返回與p共享對象的智能指針數量(只要勇調試)
定義和改變shared_ptr的其他方法
shared_ptr< T > p(q) p管理內置指針q所指向的對象,q必須質量new分配的內存切可以轉換爲T*內型
shared_ptr< T > p(p2,d) p是p2的拷貝,唯一區別是p將使用可調用對象d來代替delete(lambda表達式)
shared_ptr< T > p(u) p從unique_ptr u處接管了對象所有權,將u置爲空
shared_ptr< T > p(q,d) p將使用可調用對象d來代替delete(lambda表達式)
p.reset() p若是唯一指向器對象的shared_ptr,reset將釋放此對象
p.reset(q) 若釋放後,傳遞了q,會令p指向q
p.reset(q,d) 結合以上兩種方法,使用lambda表達式用d代替delete

對於shared_ptr< T >p(q)的操作 和 p=q的拷貝和賦值操作,我們要注意個對象中的計數器的變化和引用計數(reference count)的變化。

我們用一個shared_ptr去初始化另一個shared_ptr,或將它作爲參數傳遞給一個函數以及作爲函數的返回值,它所關聯的計數器就會遞增。
若給shared_ptr賦予一個新值或者是shared_ptr被銷燬,一個局部的shared_ptr離開了作用域,計數器就會遞減。

make_shared 函數

make_shared同樣也包含在memory頭文件中。
最安全的分配和使用動態內存的方法是調用一個名爲make_shared 的標準庫函數。
make_shared函數在動態內存中分配一個對象並初始化它,返回指向此對象的shared_ptr。

//p1爲一個指向值爲999的int的shared_ptr
shared_ptr<int>p1 = make_shared<int>(999);

//p2爲指向一個值初始化的int(即此int值爲0)
shared_ptr<int>p2 = make_shared<int>();

p2看出,如果傳遞任何參數,對象就會進行值初始化

shared_ptr自動銷燬所管理的對象,自動釋放相關聯的內存

當指向一個對象的最後一個shared_ptr被銷燬,shared_ptr類會自動銷燬這個對象。他通過他的析構函數完成這項工作。
若當動態對象不被使用,shared_ptr類會自動釋放動態對象,這個特性就是的動態內存的使用變得十分容易。
例如一下的factory 函數:

shared_ptr<FOO> factory (T arg)
{
	//函數內容
	//shared_ptr負責釋放內存
	return make_shared<FOO>(arg);
}

我們在use_factory函數調用它:

void use_factory(T arg)
{
	shared_ptr<FOO>p = factory(arg);
	//函數內容
}
	//在這裏,p屬於局部的變量,p離開了作用域,它指向的內存也會被自動釋放

p屬於局部的變量,p離開了作用域,它指向的內存也會被自動釋放。當p被銷燬時,將遞減引用計數檢查他是否爲0
但如果有其他的shared_ptr也同時指向這塊內存,他將不會被釋放(引用計數器檢查不爲0)

void use_factory(T arg)
{
	shared_ptr<FOO>p = factory(arg);	
	//(此例此處可以簡單的看作引用計數爲1)
	//函數內容
	return p;
	//函數反悔了p,引用計數進行了遞增操作(此處可看作引用計數爲2)
}
	//p離開了作用域,引用計數遞減但最終但不爲0(引用計數爲1)

和其特性一樣,若引用計數不爲0,他的內存都不會被釋放,因此保證shared_ptr在無用之後不再保留非常重要(避免內存浪費)。
一種要注意的情況就是,如果將shared_ptr保存在一個容器中(如vector),隨後重拍了容器,不再需要其中某些元素,這種情況下,要確保用erase函數刪除那些不再需要的shared_ptr。

unique_ptr類

unique_ptr“擁有”它所指向的對象,與shared_ptr不同,某個時刻只能由一個unique_ptr指向一個給定對象。
和shared_ptr不同的是,沒有類似make_shared的標準庫函數返回一個unique_str。
我們要定義一個unique_ptr時,需要將其綁定到一個new返回的指針上,類似shared_ptr,初始化unique_ptr必須採用直接初始化形式(unique_ptr構造函數也爲explicit)。

unique_ptr<string> p1(new string ("ABC");
unique_ptr<string> p2(p1);  //錯誤,unique_ptr不支持拷貝
unique_ptr<string> p2(p1.release());  //正確,up1用realease置爲空,p2初始化爲p1原來的指針
unique_ptr<string> p3;
p3 = p2;  //錯誤,unique_ptr不支持賦值
p2.reset(p1.release());	//正確,reset釋放了p2指向的內存,這行代碼表示將所有權從p1轉移給p2
unique_ptr操作(與shared_ptr相同操作不重複列出)
unique_ptr< T >u1 空unique_ptr,指向類型爲T對象。
unique_ptr< T , D> u2 空unique_ptr,與u1不同的是,u2會使用一個類型爲D的可調用對象來釋放他的指針
unique_ptr< T , D > u(d) 空unique_ptr,會使用類型爲D的對象d來代替delete
u = nullptr 釋放u所指向對象,u置爲空
u.realease() 放棄對指針控制權,返回指針並將u置爲空
u.reset() 釋放u所指向的對象
u.reset(q) 若提供了內置指針q,會令u指向q
u.reset(nullptr)

weak_ptr類

weak_ptr是一種不控制所指向對象生存期的正能指針,他指向一個shared_ptr管理的對象。
將一個weak_ptr綁定到一個shared_ptr不會改變目標對象的引用計數。
一旦最後一個指向該對象的shared_ptr被銷燬,對象就會被釋放,即使有weak_ptr指向該對象,他依舊會被釋放。
因此,weak_ptr有智能指針“弱”共享的特點。

weak_ptr
weak_ptr< T > w 空weak_ptr可以指向類型爲T的對象
weak_ptr< T >w(sp) 與shared_ptr sp 指向相同對象的weak_ptr,T必須能轉換爲sp所指向的類型
w = p p可以是一個shared_ptr也可以是weak_ptr,賦值後p與w共享對象
w.rese() w置爲空
w.use_count() 與w共享對象的shared_ptr的數量
w.expired() 若w.use_count()爲0,返回true,否則false
w.lock() 如果expired爲true,返回一個空shared_ptr,否則返回一個指向w的對象的shared_ptr
auto p =  make_shared<int>(666);
weak_ptr<int> wp(p);
//wp弱共享p,不會改變p引用計數

在使用weak_ptr時,由於指向的對象可能不存在,因此需要調用lock來檢查weak_ptr所指向對象是否存在,如:

if(shared_ptr<int> np== wp.lock()){  //lock在指向對象存在時返回一個指向該對象的shared_ptr
	//在if中,np與wp共享對象
	//但僅僅可以保證在此if中,該對象的共享訪問是安全的
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章