【C++】智能指针详解
相關博文《C++ STL 四種智能指針》
參考資料:《C++ Primer中文版 第五版》
我們知道除了靜態(tài)內存和棧內存外,每個程序還有一個內存池,這部分內存被稱為自由空間或者堆。程序用堆來存儲動態(tài)分配的對象即那些在程序運行時分配的對象,當動態(tài)對象不再使用時,我們的代碼必須顯式的銷毀它們。
在C++中,動態(tài)內存的管理是用一對運算符完成的:new和delete,new:在動態(tài)內存中為對象分配一塊空間并返回一個指向該對象的指針,delete:指向一個動態(tài)獨享的指針,銷毀對象,并釋放與之關聯(lián)的內存。
動態(tài)內存管理經常會出現兩種問題:一種是忘記釋放內存,會造成內存泄漏;一種是尚有指針引用內存的情況下就釋放了它,就會產生引用非法內存的指針。
為了更加容易(更加安全)的使用動態(tài)內存,引入了智能指針的概念。智能指針的行為類似常規(guī)指針,重要的區(qū)別是它負責自動釋放所指向的對象。標準庫提供的兩種智能指針的區(qū)別在于管理底層指針的方法不同,shared_ptr允許多個指針指向同一個對象,unique_ptr則“獨占”所指向的對象。標準庫還定義了一種名為weak_ptr的伴隨類,它是一種弱引用,指向shared_ptr所管理的對象,這三種智能指針都定義在memory頭文件中。
#shared_ptr類
創(chuàng)建智能指針時必須提供額外的信息,指針可以指向的類型:
?
默認初始化的智能指針中保存著一個空指針。
智能指針的使用方式和普通指針類似,解引用一個智能指針返回它指向的對象,在一個條件判斷中使用智能指針就是檢測它是不是空。
如下表所示是shared_ptr和unique_ptr都支持的操作:
?如下表所示是shared_ptr特有的操作:
?make_shared函數:
最安全的分配和使用動態(tài)內存的方法就是調用一個名為make_shared的標準庫函數,此函數在動態(tài)內存中分配一個對象并初始化它,返回指向此對象的shared_ptr。頭文件和share_ptr相同,在memory中
必須指定想要創(chuàng)建對象的類型,定義格式見下面例子:
?
make_shared用其參數來構造給定類型的對象,如果我們不傳遞任何參數,對象就會進行值初始化
shared_ptr的拷貝和賦值
當進行拷貝和賦值時,每個shared_ptr都會記錄有多少個其他shared_ptr指向相同的對象。
我們可以認為每個shared_ptr都有一個關聯(lián)的計數器,通常稱其為引用計數,無論何時我們拷貝一個shared_ptr,計數器都會遞增。當我們給shared_ptr賦予一個新值或是shared_ptr被銷毀(例如一個局部的shared_ptr離開其作用域)時,計數器就會遞減,一旦一個shared_ptr的計數器變?yōu)?,它就會自動釋放自己所管理的對象。
?
當指向一個對象的最后一個shared_ptr被銷毀時,shared_ptr類會自動銷毀此對象,它是通過另一個特殊的成員函數-析構函數完成銷毀工作的,類似于構造函數,每個類都有一個析構函數。析構函數控制對象銷毀時做什么操作。析構函數一般用來釋放對象所分配的資源。shared_ptr的析構函數會遞減它所指向的對象的引用計數。如果引用計數變?yōu)?,shared_ptr的析構函數就會銷毀對象,并釋放它所占用的內存。
shared_ptr還會自動釋放相關聯(lián)的內存
當動態(tài)對象不再被使用時,shared_ptr類還會自動釋放動態(tài)對象,這一特性使得動態(tài)內存的使用變得非常容易。如果你將shared_ptr存放于一個容器中,而后不再需要全部元素,而只使用其中一部分,要記得用erase刪除不再需要的那些元素。
使用了動態(tài)生存期的資源的類:
程序使用動態(tài)內存的原因:
(1)程序不知道自己需要使用多少對象
(2)程序不知道所需對象的準確類型
(3)程序需要在多個對象間共享數據
直接管理內存
C++定義了兩個運算符來分配和釋放動態(tài)內存,new和delete,使用這兩個運算符非常容易出錯。
使用new動態(tài)分配和初始化對象
在自由空間分配的內存是無名的,因此new無法為其分配的對象命名,而是返回一個指向該對象的指針
?
此new表達式在自由空間構造一個int型對象,并返回指向該對象的指針
默認情況下,動態(tài)分配的對象是默認初始化的,這意味著內置類型或組合類型的對象的值將是未定義的,而類類型對象將用默認構造函數進行初始化。
string *ps = new string;//初始化為空string int *pi = new int;//pi指向一個未初始化的int我們可以直接使用直接初始化方式來初始化一個動態(tài)分配一個動態(tài)分配的對象。我們可以使用傳統(tǒng)的構造方式,在新標準下,也可以使用列表初始化
int *pi = new int(1024); string *ps = new string(10,'9'); vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};也可以對動態(tài)分配的對象進行初始化,只需在類型名之后跟一對空括號即可;
動態(tài)分配的const對象
const int *pci = new const int(1024); //分配并初始化一個const int const string *pcs = new const string; //分配并默認初始化一個const的空string類似其他任何const對象,一個動態(tài)分配的const對象必須進行初始化。對于一個定義了默認構造函數的類類型,其const動態(tài)對象可以隱式初始化,而其他類型的對象就必須顯式初始化。由于分配的對象就必須顯式初始化。由于分配的對象是const的,new返回的指針就是一個指向const的指針。
內存耗盡:
雖然現代計算機通常都配備大容量內村,但是自由空間被耗盡的情況還是有可能發(fā)生。一旦一個程序用光了它所有可用的空間,new表達式就會失敗。默認情況下,如果new不能分配所需的內存空間,他會拋出一個bad_alloc的異常,我們可以改變使用new的方式來阻止它拋出異常
?
我們稱這種形式的new為定位new,定位new表達式允許我們向new傳遞額外的參數,在例子中我們傳給它一個由標準庫定義的nothrow的對象,如果將nothrow傳遞給new,我們的意圖是告訴它不要拋出異常。如果這種形式的new不能分配所需內存,它會返回一個空指針。bad_alloc和nothrow都在頭文件new中。
釋放動態(tài)內存
為了防止內存耗盡,在動態(tài)內存使用完之后,必須將其歸還給系統(tǒng),使用delete歸還。
指針值和delete
我們傳遞給delete的指針必須指向動態(tài)內存,或者是一個空指針。釋放一塊并非new分配的內存或者將相同的指針釋放多次,其行為是未定義的。即使delete后面跟的是指向靜態(tài)分配的對象或者已經釋放的空間,編譯還是能夠通過,實際上是錯誤的。
動態(tài)對象的生存周期直到被釋放時為止
由shared_ptr管理的內存在最后一個shared_ptr銷毀時會被自動釋放,但是通過內置指針類型來管理的內存就不是這樣了,內置類型指針管理的動態(tài)對象,直到被顯式釋放之前都是存在的,所以調用這必須記得釋放內存。
使用new和delete管理動態(tài)內存常出現的問題:
(1)忘記delete內存
(2)使用已經釋放的對象
(3)同一塊內存釋放兩次
delete之后重置指針值
在delete之后,指針就變成了空懸指針,即指向一塊曾經保存數據對象但現在已經無效的內存的地址
有一種方法可以避免懸空指針的問題:在指針即將要離開其作用于之前釋放掉它所關聯(lián)的內存
如果我們需要保留指針可以在delete之后將nullptr賦予指針,這樣就清楚的指出指針不指向任何對象。
動態(tài)內存的一個基本問題是可能多個指針指向相同的內存
shared_ptr和new結合使用
如果我們不初始化一個智能指針,它就會被初始化成一個空指針,接受指針參數的職能指針是explicit的,因此我們不能將一個內置指針隱式轉換為一個智能指針,必須直接初始化形式來初始化一個智能指針
?
下表為定義和改變shared_ptr的其他方法:
不要混合使用普通指針和智能指針
如果混合使用的話,智能指針自動釋放之后,普通指針有時就會變成懸空指針,當將一個shared_ptr綁定到一個普通指針時,我們就將內存的管理責任交給了這個shared_ptr。一旦這樣做了,我們就不應該再使用內置指針來訪問shared_ptr所指向的內存了。
也不要使用get初始化另一個智能指針或為智能指針賦值
?
p和q指向相同的一塊內部才能,由于是相互獨立創(chuàng)建,因此各自的引用計數都是1,當q所在的程序塊結束時,q被銷毀,這會導致q指向的內存被釋放,p這時候就變成一個空懸指針,再次使用時,將發(fā)生未定義的行為,當p被銷毀時,這塊空間會被二次delete
其他shared_ptr操作
可以使用reset來將一個新的指針賦予一個shared_ptr:
?
與賦值類似,reset會更新引用計數,如果需要的話,會釋放p的對象。reset成員經常和unique一起使用,來控制多個shared_ptr共享的對象。在改變底層對象之前,我們檢查自己是否是當前對象僅有的用戶。如果不是,在改變之前要制作一份新的拷貝:
if(!p.unique()) p.reset(new string(*p));//我們不是唯一用戶,分配新的拷貝 *p+=newVal;//現在我們知道自己是唯一的用戶,可以改變對象的值
如果使用智能指針,即使程序塊過早結束,智能指針也能確保在內存不再需要時將其釋放,sp是一個shared_ptr,因此sp銷毀時會檢測引用計數,當發(fā)生異常時,我們直接管理的內存是不會自動釋放的。如果使用內置指針管理內存,且在new之后在對應的delete之前發(fā)生了異常,則內存不會被釋放。
使用我們自己的釋放操作
默認情況下,shared_ptr假定他們指向的是動態(tài)內存,因此當一個shared_ptr被銷毀時,會自動執(zhí)行delete操作,為了用shared_ptr來管理一個connection,我們必須首先必須定義一個函數來代替delete。這個刪除器函數必須能夠完成對shared_ptr中保存的指針進行釋放的操作。
智能指針陷阱:
(1)不使用相同的內置指針值初始化(或reset)多個智能指針。
(2)不delete get()返回的指針
(3)不使用get()初始化或reset另一個智能指針
(4)如果你使用get()返回的指針,記住當最后一個對應的智能指針銷毀后,你的指針就變?yōu)闊o效了
(5)如果你使用智能指針管理的資源不是new分配的內存,記住傳遞給它一個刪除器
#unique_ptr
某個時刻只能有一個unique_ptr指向一個給定對象,由于一個unique_ptr擁有它指向的對象,因此unique_ptr不支持普通的拷貝或賦值操作。
下表是unique的操作:
?
雖然我們不能拷貝或者賦值unique_ptr,但是可以通過調用release或reset將指針所有權從一個(非const)unique_ptr轉移給另一個unique
//將所有權從p1(指向string Stegosaurus)轉移給p2 unique_ptr<string> p2(p1.release());//release將p1置為空 unique_ptr<string>p3(new string("Trex")); //將所有權從p3轉移到p2 p2.reset(p3.release());//reset釋放了p2原來指向的內存?release成員返回unique_ptr當前保存的指針并將其置為空。因此,p2被初始化為p1原來保存的指針,而p1被置為空。
reset成員接受一個可選的指針參數,令unique_ptr重新指向給定的指針。
調用release會切斷unique_ptr和它原來管理的的對象間的聯(lián)系。release返回的指針通常被用來初始化另一個智能指針或給另一個智能指針賦值。
不能拷貝unique_ptr有一個例外:我們可以拷貝或賦值一個將要被銷毀的unique_ptr.最常見的例子是從函數返回一個unique_ptr.
?
還可以返回一個局部對象的拷貝:
unique_ptr<int> clone(int p) {unique_ptr<int> ret(new int(p));return ret; }向后兼容:auto_ptr
標準庫的較早版本包含了一個名為auto_ptr的類,它具有uniqued_ptr的部分特性,但不是全部。
用unique_ptr傳遞刪除器
unique_ptr默認使用delete釋放它指向的對象,我們可以重載一個unique_ptr中默認的刪除器
我們必須在尖括號中unique_ptr指向類型之后提供刪除器類型。在創(chuàng)建或reset一個這種unique_ptr類型的對象時,必須提供一個指定類型的可調用對象刪除器。
#weak_ptr
weak_ptr是一種不控制所指向對象生存期的智能指針,它指向一個由shared_ptr管理的對象,將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數。一旦最后一個指向對象的shared_ptr被銷毀,對象就會被釋放,即使有weak_ptr指向對象,對象還是會被釋放。
weak_ptr的操作
?
由于對象可能不存在,我們不能使用weak_ptr直接訪問對象,而必須調用lock,此函數檢查weak_ptr指向的對象是否存在。如果存在,lock返回一個指向共享對象的shared_ptr,如果不存在,lock將返回一個空指針
#scoped_ptr
scoped和weak_ptr的區(qū)別就是,給出了拷貝和賦值操作的聲明并沒有給出具體實現,并且將這兩個操作定義成私有的,這樣就保證scoped_ptr不能使用拷貝來構造新的對象也不能執(zhí)行賦值操作,更加安全,但有了"++""–"以及“*”“->”這些操作,比weak_ptr能實現更多功能。
?
原文鏈接:https://blog.csdn.net/flowing_wind/article/details/81301001
總結
以上是生活随笔為你收集整理的【C++】智能指针详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 脉脉高聘:超八成猎头不到35岁,六成猎头
- 下一篇: 杨元庆过去十天四度减持联想股票 套现1.