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