C++知識點:智能指針

以前面試蝦皮時問到了智能指針相關知識點,當時答的很沒有條理,這裏整理一下權當筆記。面試

根據《C++ Primer Plus》中的解釋,智能指針是行爲相似於指針的模板對象。當函數分配堆內存時,必定要記得函數結束前回收內存。可是若是函數異常停止,本地變量都將從棧內存中刪除,以下例,此時ps指針佔據的內存也會被釋放,從而致使資源泄露。所以若是指針ps有一個析構函數,在ps過時時自動釋放它管理的內存,這就是智能指針的思想。數組


auto_ptr安全

auto_ptr目前已被拋棄。參考下例,當咱們對auto_ptr類型的智能指針進行賦值時,原來的對象ps被接管成爲空指針,後續操做ps就會致使core dump。就算不操做ps,由於兩個指針指向同一對象,會致使該對象被刪除兩次。微信


auto_ptr被explicit關鍵字修飾,意味着智能指針只能被顯示轉換(也就是不能被隱式強制轉換)。判斷一個智能指針是否爲空不能使用if(ps== NULL),而應該使用if(ps.get() == NULL),由於ps是指針對象,不是指針。函數

unique_ptrui

unique_ptr 是一個獨享全部權的智能指針,它提供了嚴格意義上的全部權,包括:spa

一、擁有它指向的對象;.net

二、沒法進行復制構造和複製賦值操做。即沒法使兩個unique_ptr指向同一個對象。可是能夠進行移動構造(std::move)和移動賦值操做,也就是淺拷貝;線程

三、保存指向某個對象的指針,當它自己被刪除釋放的時候,會使用給定的刪除器釋放它指向的對象;3d

仍是上面的例子,這時候編譯器會認爲賦值語句非法,避免了ps再也不指向有效數據的問題,所以unique_ptr比auto_ptr更安全(編譯階段錯誤比運行階段程序crash更安全)。


不過對於返回值爲unique_ptr類型的函數,能夠用另外一個unique_ptr智能指針接收函數返回值。由於函數返回的臨時變量很快被銷燬,程序沒法使用它來訪問無效的數據。

unique_ptr還有另外一個優勢。auto_ptr使用delete而不是delete[],所以只能與new搭配使用;而unique_ptr使用delete[],能夠與new[]搭配使用,所以能夠用於數組。

shared_ptr

shared_ptr使用計數機制來代表資源被幾個指針共享。能夠經過成員函數use_count()來查看資源的全部者個數。除了能夠經過new來構造,還能夠經過傳入auto_ptr, unique_ptr,weak_ptr來構造。


咱們來看一下shared_ptr智能指針的賦值操做,p2 = p1;p2管理的對象計數值減1(但不必定釋放對象),p1管理的對象計數值加1。若是p2管理的對象計數值爲0,則自動釋放管理的對象。此時無論p2以前是否初始化(未賦值),p1與p2管理相同的對象(也就是原來p1管理的對象),所以賦值後p1與p2計數一致,獲得:

p1.use_count()==p2.use_count();

有個問題,p2指向p2管理的對象以後,它原來管理的對象怎麼辦呢?若是資源A計數值爲0(說明已經沒有別的指針指向它了),資源A會被自動釋放;若是計數值不爲0(說明還有其餘指針管理着它),那麼就不用管。


weak_ptr

weak_ptr是用來解決shared_ptr相互引用時的死鎖問題。若是兩個shared_ptr相互引用,那麼這兩個指針的引用計數永遠不可能將爲0,這時資源永遠不會釋放。而weak_ptr是對對象的一種弱引用,不會增長對象的引用計數。此外weak_ptr和shared_ptr能夠相互轉化,shared_ptr能夠直接賦值給它,它能夠經過調用lock()函數來得到shared_ptr。


上面這個例子,類A和類B分別持有對方的智能指針,對象pa和pb相互引用後各自資源的引用計數都爲2。當跳出函數時,智能指針pa、pb前後析構,引用計數減一,可是此時二者計數仍是爲1,致使函數結束時資源沒有被釋放。此時若是咱們把類A裏面的shared_ptr<B> pb_改成weak_ptr<B>pb_,這樣資源B的引用計數就只有1,當pb析構時,B的計數變爲0,B獲得釋放,B釋放的同時也會使A的計數減一,同時pa析構時使A的計數減一,那麼A的計數爲0,A獲得釋放。

注意的是咱們不能經過weak_ptr直接訪問對象的方法,好比B對象中有一個方法print(),咱們不能經過pb->print(); 由於pb是一個weak_ptr,應該先把它轉化爲shared_ptr,即:shared_ptr<B> p =pb.lock();    p->print();

對於函數中的多個變量,最早申請的對象最後釋放。說到函數棧空間,咱們說函數棧釋放,實際是指寄存器pc地址返回而已,棧空間的數據仍是存在的,以下例:


調用fun1返回以後,雖然棧變量a被釋放,可是fun2進入函數調用立刻入棧,由於使用的是同一塊棧空間,所以當fun2內的臨時變量未初始化時,內存裏面的髒數據就是fun1殘留下來的,上面的示例中fun2的變量b若是不自行初始化,那初始值就是1。

智能指針shared_ptr雖然能解決資源自動回收,但並非線程安全的,通俗的講就是修改指向資源的指針和修改計數值是兩步操做,須要mutex保護。

另外若是是將原始指針強制轉換未智能指針的話,由於原始資源(例子中未widget)已經分配好了,那麼shared_ptr只能未引用計數再單獨分配控制塊。

 auto p = new widget(); shared_ptr sp1{ p }; ...

若是選擇使用 make_shared 的話,原始資源和引用計數的分配, 能夠一次性完成,減小了內存分配的次數。

最後,祝你們雙十一多買多賣。

本文分享自微信公衆號 - 機械猿(on_ourway)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索