c++ 智能指針用法詳解

http://www.cnblogs.com/TenosDoIt/p/3456704.html

本文介紹c++里面的四個智能指針: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三個是c++11支持,并且第一個已經被c++11棄用。

為什么要使用智能指針:我們知道c++的內存管理是讓很多人頭疼的事,當我們寫一個new語句時,一般就會立即把delete語句直接也寫了,但是我們不能避免程序還未執行到delete時就跳轉了或者在函數中沒有執行到最后的delete語句就返回了,如果我們不在每一個可能跳轉或者返回的語句前釋放資源,就會造成內存泄露。使用智能指針可以很大程度上的避免這個問題,因為智能指針就是一個類,當超出了類的作用域是,類會自動調用析構函數,析構函數會自動釋放資源。下面我們逐個介紹。

auto_ptr (官方文檔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class?Test
{
public:
????Test(string s)
????{
????????str = s;
???????cout<<"Test creat\n";
????}
????~Test()
????{
????????cout<<"Test delete:"<<str<<endl;
????}
????string& getStr()
????{
????????return?str;
????}
????void?setStr(string s)
????{
????????str = s;
????}
????void?print()
????{
????????cout<<str<<endl;
????}
private:
????string str;
};
int?main()
{
????auto_ptr<Test> ptest(new?Test("123"));
????ptest->setStr("hello ");
????ptest->print();
????ptest.get()->print();
????ptest->getStr() += "world !";
????(*ptest).print();
????ptest.reset(new?Test("123"));
????ptest->print();
????return?0;
}

運行結果如下

image

如上面的代碼:智能指針可以像類的原始指針一樣訪問類的public成員,成員函數get()返回一個原始的指針,成員函數reset()重新綁定指向的對象,而原來的對象則會被釋放。注意我們訪問auto_ptr的成員函數時用的是“.”,訪問指向對象的成員時用的是“->”。我們也可用聲明一個空智能指針auto_ptr<Test>ptest();

當我們對智能指針進行賦值時,如ptest2 = ptest,ptest2會接管ptest原來的內存管理權,ptest會變為空指針,如果ptest2原來不為空,則它會釋放原來的資源,基于這個原因,應該避免把auto_ptr放到容器中,因為算法對容器操作時,很難避免STL內部對容器實現了賦值傳遞操作,這樣會使容器中很多元素被置為NULL。判斷一個智能指針是否為空不能使用if(ptest == NULL),應該使用if(ptest.get() == NULL),如下代碼???????????????????????????????????????????????????????????本文地址

1
2
3
4
5
6
7
8
9
int?main()
{
????auto_ptr<Test> ptest(new?Test("123"));
????auto_ptr<Test> ptest2(new?Test("456"));
????ptest2 = ptest;
????ptest2->print();
????if(ptest.get() == NULL)cout<<"ptest = NULL\n";
????return?0;
}

image

還有一個值得我們注意的成員函數是release,這個函數只是把智能指針賦值為空,但是它原來指向的內存并沒有被釋放,相當于它只是釋放了對資源的所有權,從下面的代碼執行結果可以看出,析構函數沒有被調用。

1
2
3
4
5
6
int?main()
{
????auto_ptr<Test> ptest(new?Test("123"));
????ptest.release();
????return?0;
}

image

那么當我們想要在中途釋放資源,而不是等到智能指針被析構時才釋放,我們可以使用ptest.reset(); 語句。


unique_ptr (官方文檔)?

?

?

unique_ptr,是用于取代c++98的auto_ptr的產物,在c++98的時候還沒有移動語義(move semantics)的支持,因此對于auto_ptr的控制權轉移的實現沒有核心元素的支持,但是還是實現了auto_ptr的移動語義,這樣帶來的一些問題是拷貝構造函數和復制操作重載函數不夠完美,具體體現就是把auto_ptr作為函數參數,傳進去的時候控制權轉移,轉移到函數參數,當函數返回的時候并沒有一個控制權移交的過程,所以過了函數調用則原先的auto_ptr已經失效了.在c++11當中有了移動語義,使用move()把unique_ptr傳入函數,這樣你就知道原先的unique_ptr已經失效了.移動語義本身就說明了這樣的問題,比較坑爹的是標準描述是說對于move之后使用原來的內容是未定義行為,并非拋出異常,所以還是要靠人肉遵守游戲規則.再一個,auto_ptr不支持傳入deleter,所以只能支持單對象(delete object),而unique_ptr對數組類型有偏特化重載,并且還做了相應的優化,比如用[]訪問相應元素等.

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

1、擁有它指向的對象

2、無法進行復制構造,無法進行復制賦值操作。即無法使兩個unique_ptr指向同一個對象。但是可以進行移動構造和移動賦值操作

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

unique_ptr 可以實現如下功能:

1、為動態申請的內存提供異常安全

2、講動態申請的內存所有權傳遞給某函數

3、從某個函數返回動態申請內存的所有權

4、在容器中保存指針

5、auto_ptr 應該具有的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unique_ptr<Test> fun()
{
????return?unique_ptr<Test>(new?Test("789"));
}
int?main()
{
????unique_ptr<Test> ptest(new?Test("123"));
????unique_ptr<Test> ptest2(new?Test("456"));
????ptest->print();
????ptest2 = std::move(ptest);//不能直接ptest2 = ptest
????if(ptest == NULL)cout<<"ptest = NULL\n";
????Test* p = ptest2.release();
????p->print();
????ptest.reset(p);
????ptest->print();
????ptest2 = fun(); //這里可以用=,因為使用了移動構造函數
????ptest2->print();
????return?0;
}

image

unique_ptr 和 auto_ptr用法很相似,不過不能使用兩個智能指針賦值操作,應該使用std::move; 而且它可以直接用if(ptest == NULL)來判斷是否空指針;release、get、reset等用法也和auto_ptr一致,使用函數的返回值賦值時,可以直接使用=, 這里使用c++11 的移動語義特性。另外注意的是當把它當做參數傳遞給函數時(使用值傳遞,應用傳遞時不用這樣),傳實參時也要使用std::move,比如foo(std::move(ptest))。它還增加了一個成員函數swap用于交換兩個智能指針的值


share_ptr?(官方文檔)

從名字share就可以看出了資源可以被多個指針共享,它使用計數機制來表明資源被幾個指針共享。可以通過成員函數use_count()來查看資源的所有者個數。出了可以通過new來構造,還可以通過傳入auto_ptr, unique_ptr,weak_ptr來構造。當我們調用release()時,當前指針會釋放資源所有權,計數減一。當計數等于0時,資源會被釋放。具體的成員函數解釋可以參考?here

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int?main()
{
????shared_ptr<Test> ptest(new?Test("123"));
????shared_ptr<Test> ptest2(new?Test("456"));
????cout<<ptest2->getStr()<<endl;
????cout<<ptest2.use_count()<<endl;
????ptest = ptest2;//"456"引用次數加1,“123”銷毀
????ptest->print();
????cout<<ptest2.use_count()<<endl;//2
????cout<<ptest.use_count()<<endl;//2
????ptest.reset();
????ptest2.reset();//此時“456”銷毀
????cout<<"done !\n";
????return?0;
}

image


weak_ptr(官方文檔)

weak_ptr是用來解決shared_ptr相互引用時的死鎖問題,如果說兩個shared_ptr相互引用,那么這兩個指針的引用計數永遠不可能下降為0,資源永遠不會釋放。它是對對象的一種弱引用,不會增加對象的引用計數,和shared_ptr之間可以相互轉化,shared_ptr可以直接賦值給它,它可以通過調用lock函數來獲得shared_ptr。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class?B;
class?A
{
public:
????shared_ptr<B> pb_;
????~A()
????{
????????cout<<"A delete\n";
????}
};
class?B
{
public:
????shared_ptr<A> pa_;
????~B()
????{
????????cout<<"B delete\n";
????}
};
void?fun()
{
????shared_ptr<B> pb(new?B());
????shared_ptr<A> pa(new?A());
????pb->pa_ = pa;
????pa->pb_ = pb;
????cout<<pb.use_count()<<endl;
????cout<<pa.use_count()<<endl;
}
int?main()
{
????fun();
????return?0;
}

image

可以看到fun函數中pa ,pb之間互相引用,兩個資源的引用計數為2,當要跳出函數時,智能指針pa,pb析構時兩個資源引用計數會減一,但是兩者引用計數還是為1,導致跳出函數時資源沒有被釋放(A B的析構函數沒有被調用),如果把其中一個改為weak_ptr就可以了,我們把類A里面的shared_ptr<B> pb_; 改為weak_ptr<B> pb_; 運行結果如下,這樣的話,資源B的引用開始就只有1,當pb析構時,B的計數變為0,B得到釋放,B釋放的同時也會使A的計數減一,同時pa析構時使A的計數減一,那么A的計數為0,A得到釋放。

image

注意的是我們不能通過weak_ptr直接訪問對象的方法,比如B對象中有一個方法print(),我們不能這樣訪問,pa->pb_->print(); 英文pb_是一個weak_ptr,應該先把它轉化為shared_ptr,如:shared_ptr<B> p = pa->pb_.lock();??? p->print();

?

參考資料

胡健:http://www.cnblogs.com/hujian/archive/2012/12/10/2810776.html

胡健:http://www.cnblogs.com/hujian/archive/2012/12/10/2810754.html

胡健:http://www.cnblogs.com/hujian/archive/2012/12/10/2810785.html

天方:http://www.cnblogs.com/TianFang/archive/2008/09/20/1294590.html

gaa_ra:http://blog.csdn.net/gaa_ra/article/details/7841204

cplusplus:http://www.cplusplus.com/

?

【版權聲明】轉載請注明出處:http://www.cnblogs.com/TenosDoIt/p/3456704.html


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/384118.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/384118.shtml
英文地址,請注明出處:http://en.pswp.cn/news/384118.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CodeForces - 786BLegacy——線段樹建圖+最短路

【題目描述】 CodeForces - 786BLegacy 【題目分析】 題目大概意思就是有三種操作&#xff1a; 從某個點到另一個點從某個點到另一個區間從某個區間到另一個點 然后詢問從其中一個點到其他所有點的距離——這很顯然是一個求單源最短路徑的。我們簡單的想法顯然是建一個圖&a…

自主編寫shell

1.替換原理 用fork創建子進程后執行的是和父進程相同的程序&#xff08;但有可能執行不同的代碼分支&#xff09;&#xff0c;子進程往往要調用一種exec函數以執行例外一個程序。當進程調用一種exec函數時&#xff0c;該進程的用戶空間代碼和數據完全被新程序替換&#xff0c;從…

HYSBZ - 2243染色——樹鏈剖分+線段樹建樹技巧

【題目描述】 HYSBZ - 2243染色 【題目分析】 我一直沒有看清楚題&#xff0c;以為求的是路徑上出現顏色的種類&#xff0c;然后就寫了一個區間染色的線段樹進行維護&#xff0c;過樣例的時候才發現題讀錯了&#xff0c;人家要求的是路徑上出現的顏色段&#xff0c;所以顏色的…

右值引用與轉移語義

https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/ 新特性的目的 右值引用 (Rvalue Referene) 是 C 新標準 (C11, 11 代表 2011 年 ) 中引入的新特性 , 它實現了轉移語義 (Move Sementics) 和精確傳遞 (Perfect Forwarding)。它的主要目的有兩個方面&#xff…

打動態庫和靜態庫

一.動態庫和靜態庫的定義 1.靜態庫 ????程序在編譯鏈接時把庫的代碼鏈接到可執行文件中。程序運行時就不再需要靜態庫 2.動態庫 ????程序在運行的時候才去鏈接動態庫的代碼&#xff0c;多個程序 共享使用代碼 3.動態鏈接 ????在執行文件之前&#xff0c;外部…

HYSBZ - 2157樹鏈剖分

【題目描述】 HYSBZ - 2157樹鏈剖分 【題目分析】 這道題給出的是邊權而不是點權&#xff0c;但是我們分析這個樹就會發現每個節點都只有一個父親&#xff0c;也就是每條邊的邊權都可以存放在兒子節點上&#xff0c;然后在遍歷路徑的時候我們在從前往后遍歷&#xff0c;但是注…

C++11中的右值引用

http://www.cnblogs.com/yanqi0124/p/4723698.html 在C98中有左值和右值的概念&#xff0c;不過這兩個概念對于很多程序員并不關心&#xff0c;因為不知道這兩個概念照樣可以寫出好程序。在C11中對右值的概念進行了增強&#xff0c;我個人理解這部分內容是C11引入的特性中最難以…

BZOJ2115XOR——線性基

【題目描述】 BZOJ2115XOR——線性基 【題目分析】 這道題看完以后很懵逼&#xff0c;人家要是走的很復雜呢&#xff1f;各種繞來繞去怎么辦&#xff1f; 首先我們應該注意到一個很明顯的道理&#xff1a;重復的路徑會和自身抵消&#xff0c;所以我們大可以隨便跑&#xff0c;…

單鏈表的相關操作

1.冒泡排序對單鏈表進行排序 void LinkListBubbleSort(LinkNode* head) {if(head NULL){ return;//空鏈表} if(head -> next NULL){ return;//只有一個結點} LinkNode* cur head;//趟數LinkNode* tail NULL;//尾指針LinkNode* tmp head;//次數for(; cur -…

socket網絡編程--epoll小結

http://www.cnblogs.com/wunaozai/p/3895860.html 以前使用的用于I/O多路復用為了方便就使用select函數&#xff0c;但select這個函數是有缺陷的。因為它所支持的并發連接數是有限的(一般小于1024)&#xff0c;因為用戶處理的數組是使用硬編碼的。這個最大值為FD_SETSIZE&#…

進程間通信(匿名管道)

1.進程通信的目的 (1) 數據傳輸: 一個進程需要將它的數據傳輸給另一個進程 ????(2) 資源共享: 多個進程之間共享同樣的資源 ????(3) 通知事件: 一個進程需要向另一個或一組進程發送消息, 通知它們發生了什么事情 2.管道 管道是一種進程之間通信的一種方式, 我們把從…

線性基入門

今天學習了神奇的線性基&#xff0c;主要是在解決異或問題時比較有用。 詳細的解釋和證明有大佬珠玉在前&#xff0c;如果感興趣可以移步 補充一下自己的理解&#xff1a; 可以聯系線性代數極大無關組進行理解&#xff0c;線性基就相當于異或的向量空間中的極大無關組&#xff…

單例模式及C++實現代碼

http://www.cnblogs.com/cxjchen/p/3148582.html 單例模式 單例模式&#xff0c;可以說設計模式中最常應用的一種模式了&#xff0c;據說也是面試官最喜歡的題目。但是如果沒有學過設計模式的人&#xff0c;可能不會想到要去應用單例模式&#xff0c;面對單例模式適用的情況&am…

UVALive - 8512——線段樹維護線性基

【題目描述】 UVALive - 8512XOR 【題目分析】 這種區間線性基的問題我們可以考慮用線段樹維護&#xff0c;線性基的合并的話就直接暴力合并 找到所在區間的線性基后再查找最大的數&#xff0c;我看網上的博客要說消除k的影響什么的&#xff0c;我覺得沒有什么必要&#xff0c;…

命名管道

1.命名管道的創建 (1) 通過命令創建 mkfifo filename (2)在程序中創建 int mkfifo(const char* filename, mode_t mode); 2. 命名管道和匿名管道的區別 (1)匿名管道由pipe函數創建并且打開 ????(2)命名管道有mkfifo函數創建由open函數打開 ????(3) fifo 之間的兩…

HYSBZ - 1101——莫比烏斯反演

【題目描述】 HYSBZ - 1101 【題目分析】 昨天測試出了一道差不多的題目&#xff0c;我只能想到暴力&#xff0c;各種優化&#xff0c;最后都是運行了好久TLE&#xff0c;最后才知道要用到莫比烏斯反演&#xff0c;就想著今天研究一下&#xff0c;得出的結論就是&#xff0c;我…

Linux下I/O多路轉接之select --fd_set

http://blog.csdn.net/li_ning_/article/details/52165993 fd_set 你終于還是來了&#xff0c;能看到這個標題進來的&#xff0c;我想&#xff0c;你一定是和我遇到了一樣的問題&#xff0c;一樣的疑惑&#xff0c;接下來幾個小時&#xff0c;我一定竭盡全力&#xff0c;寫出我…

BZOJ 2844 | HYSBZ - 2844albus就是要第一個出場——線性基

【題目描述】 BZOJ 2844 | HYSBZ - 2844albus 【題目分析】 題目的意思大概是給一個數列&#xff0c;他有2n個子集&#xff0c;每個子集的元素的異或和構成新的一個數列&#xff0c;排序后問數字Q在這個序列里面的下標。 假如題目是求所有元素的異或和構成一個集合就好弄了&…

CodeForces - 641ELittle Artem and Time Machine——map+樹狀數組

【題目描述】 CodeForces - 641ELittle Artem and Time Machine 【題目分析】 題目的意思大概是有三種操作 1.在時間t加入一個數字x 2.在時間t刪除一個數字x 3.詢問在時間t集合里面x的個數 雖然題目描述很簡單&#xff0c;但是t和x的范圍都是109&#xff0c;我一開始想到的是主…

I/O多路轉接之poll 函數

http://blog.csdn.net/li_ning_/article/details/52167224 poll 一、poll()函數&#xff1a; 這個函數是某些Unix系統提供的用于執行與select()函數同等功能的函數&#xff0c;自認為poll和select大同小異&#xff0c;下面是這個函數的聲明&#xff1a; [cpp] view plaincopy …