c++智能指針的超詳細(xì)講解
1.什么是智能指針
從比較簡(jiǎn)單的層面來(lái)看,智能指針是RAII(Resource Acquisition Is Initialization,資源獲取即初始化)機(jī)制對(duì)普通指針進(jìn)行的一層封裝。這樣使得智能指針的行為動(dòng)作像一個(gè)指針,本質(zhì)上卻是一個(gè)對(duì)象,這樣可以方便管理一個(gè)對(duì)象的生命周期。
在c++中,智能指針一共定義了4種:
auto_ptr、unique_ptr、shared_ptr 和 weak_ptr。其中,auto_ptr 在 C++11已被摒棄,在C++17中已經(jīng)移除不可用。
2.原始指針的問(wèn)題
原始指針的問(wèn)題大家都懂,就是如果忘記刪除,或者刪除的情況沒(méi)有考慮清楚,容易造成懸掛指針(dangling pointer)或者說(shuō)野指針(wild pointer)。
我們看個(gè)簡(jiǎn)單的例子
objtype *p = new objtype(); p -> func(); delete p;
上面的代碼結(jié)構(gòu)是我們經(jīng)常看到的。里面的問(wèn)題主要有以下兩點(diǎn):
1.代碼的最后,忘記執(zhí)行delete p的操作。
2.第一點(diǎn)其實(shí)還好,比較容易發(fā)現(xiàn)也比較容易解決。比較麻煩的是,如果func()中有異常,delete p語(yǔ)句執(zhí)行不到,這就很難辦。有的同學(xué)說(shuō)可以在func中進(jìn)行刪除操作,理論上是可以這么做,實(shí)際操作起來(lái),會(huì)非常麻煩也非常復(fù)雜。
此時(shí),智能指針就可以方便我們控制指針對(duì)象的生命周期。在智能指針中,一個(gè)對(duì)象什么情況下被析構(gòu)或被刪除,是由指針本身決定的,并不需要用戶進(jìn)行手動(dòng)管理,是不是瞬間覺(jué)得幸福感提升了一大截,有點(diǎn)幸福來(lái)得太突然的意思,終于不用我自己手動(dòng)刪除指針了。
3.unique_ptr
unique_ptr是獨(dú)享被管理對(duì)象指針?biāo)袡?quán)(owership)的智能指針。unique_ptr對(duì)象封裝一個(gè)原始指針,并負(fù)責(zé)其生命周期。當(dāng)該對(duì)象被銷毀時(shí),會(huì)在其析構(gòu)函數(shù)中刪除關(guān)聯(lián)的原始指針。
創(chuàng)建unique_ptr:
#include <iostream> #include <string> #include <memory> using namespace std; void f1() { unique_ptr<int> p(new int(5)); cout<<*p<<endl; }
上面的代碼就創(chuàng)建了一個(gè)unique_ptr。需要注意的是,unique_ptr沒(méi)有復(fù)制構(gòu)造函數(shù),不支持普通的拷貝和賦值操作。因?yàn)閡nique_ptr獨(dú)享被管理對(duì)象指針?biāo)袡?quán),當(dāng)p2, p3失去p的所有權(quán)時(shí)會(huì)釋放對(duì)應(yīng)資源,此時(shí)會(huì)執(zhí)行兩次delete p的操作。
void f1() { unique_ptr<int> p(new int(5)); cout<<*p<<endl; unique_ptr<int> p2(p); unique_ptr<int> p3 = p; }
對(duì)于p2,p3對(duì)應(yīng)的行,IDE會(huì)提示報(bào)錯(cuò)
無(wú)法引用 函數(shù) "std::__1::unique_ptr<_Tp, _Dp>::unique_ptr(const std::__1::unique_ptr<int, std::__1::default_delete<int>> &) [其中 _Tp=int, _Dp=std::__1::default_delete<int>]" (已隱式聲明) -- 它是已刪除的函數(shù)
unique_ptr雖然不支持普通的拷貝和賦值操作,但卻可以將所有權(quán)進(jìn)行轉(zhuǎn)移,使用std::move方法即可。
void f1() { unique_ptr<int> p(new int(5)); unique_ptr<int> p2 = std::move(p); //error,此時(shí)p指針為空: cout<<*p<<endl; cout<<*p2<<endl; }
unique最常見(jiàn)的使用場(chǎng)景,就是替代原始指針,為動(dòng)態(tài)申請(qǐng)的資源提供異常安全保證。
objtype *p = new objtype(); p -> func(); delete p
前面我們分析了這部分代碼的問(wèn)題,如果我們修改一下
unique_ptr<objtype> p(new objtype()); p -> func(); delete p
此時(shí)我們只要unique_ptr創(chuàng)建成功,unique_ptr對(duì)應(yīng)的析構(gòu)函數(shù)都能保證被調(diào)用,從而保證申請(qǐng)的動(dòng)態(tài)資源能被釋放掉。
4.shared_ptr
我們提到的智能指針,很大程度上就是指的shared_ptr,shared_ptr也在實(shí)際應(yīng)用中廣泛使用。它的原理是使用引用計(jì)數(shù)實(shí)現(xiàn)對(duì)同一塊內(nèi)存的多個(gè)引用。在最后一個(gè)引用被釋放時(shí),指向的內(nèi)存才釋放,這也是和 unique_ptr 最大的區(qū)別。當(dāng)對(duì)象的所有權(quán)需要共享(share)時(shí),share_ptr可以進(jìn)行賦值拷貝。
shared_ptr使用引用計(jì)數(shù),每一個(gè)shared_ptr的拷貝都指向相同的內(nèi)存。每使用他一次,內(nèi)部的引用計(jì)數(shù)加1,每析構(gòu)一次,內(nèi)部的引用計(jì)數(shù)減1,減為0時(shí),刪除所指向的堆內(nèi)存。
std::shared_ptr<int> p4 = new int(1)
上面這種寫法是錯(cuò)誤的,因?yàn)橛疫叺玫降氖且粋€(gè)原始指針,前面我們講過(guò)shared_ptr本質(zhì)是一個(gè)對(duì)象,將一個(gè)指針賦值給一個(gè)對(duì)象是不行的。
void f2() { shared_ptr<int> p = make_shared<int>(1); shared_ptr<int> p2(p); shared_ptr<int> p3 = p; }
以上寫法都是可以的
void f2() { shared_ptr<int> p = make_shared<int>(1); int *p2 = p.get(); cout<<*p2<<endl; }
上面的寫法,可以獲取shared_ptr的原始指針。
5.shared_ptr使用需要注意的點(diǎn)
5.1 不能將一個(gè)原始指針初始化多個(gè)shared_ptr
void f2() { int *p0 = new int(1); shared_ptr<int> p1(p0); shared_ptr<int> p2(p0); cout<<*p1<<endl; }
上面代碼就會(huì)報(bào)錯(cuò)。原因也很簡(jiǎn)單,因?yàn)閜1,p2都要進(jìn)行析構(gòu)刪除,這樣會(huì)造成原始指針p0被刪除兩次,自然要報(bào)錯(cuò)。
5.2.循環(huán)引用問(wèn)題
shared_ptr最大的坑就是循環(huán)引用。引用網(wǎng)絡(luò)上的一個(gè)例子:
struct Father { shared_ptr<Son> son_; }; struct Son { shared_ptr<Father> father_; }; int main() { auto father = make_shared<Father>(); auto son = make_shared<Son>(); father->son_ = son; son->father_ = father; return 0; }
該部分代碼會(huì)有內(nèi)存泄漏問(wèn)題。原因是
1.main 函數(shù)退出之前,F(xiàn)ather 和 Son 對(duì)象的引用計(jì)數(shù)都是 2。
2.son 指針?shù)N毀,這時(shí) Son 對(duì)象的引用計(jì)數(shù)是 1。
3.father 指針?shù)N毀,這時(shí) Father 對(duì)象的引用計(jì)數(shù)是 1。
4.由于 Father 對(duì)象和 Son 對(duì)象的引用計(jì)數(shù)都是 1,這兩個(gè)對(duì)象都不會(huì)被銷毀,從而發(fā)生內(nèi)存泄露。
為避免循環(huán)引用導(dǎo)致的內(nèi)存泄露,就需要使用 weak_ptr。weak_ptr 并不擁有其指向的對(duì)象,也就是說(shuō),讓 weak_ptr 指向 shared_ptr 所指向?qū)ο?,?duì)象的引用計(jì)數(shù)并不會(huì)增加。
使用 weak_ptr 就能解決前面提到的循環(huán)引用的問(wèn)題,方法很簡(jiǎn)單,只要讓 Son 或者 Father 包含的 shared_ptr 改成 weak_ptr 就可以了。
struct Father { shared_ptr<Son> son_; }; struct Son { weak_ptr<Father> father_; }; int main() { auto father = make_shared<Father>(); auto son = make_shared<Son>(); father->son_ = son; son->father_ = father; return 0; }
1.main 函數(shù)退出前,Son 對(duì)象的引用計(jì)數(shù)是 2,而 Father 的引用計(jì)數(shù)是 1。
2.son 指針?shù)N毀,Son 對(duì)象的引用計(jì)數(shù)變成 1。
3.father 指針?shù)N毀,F(xiàn)ather 對(duì)象的引用計(jì)數(shù)變成 0,導(dǎo)致 Father 對(duì)象析構(gòu),F(xiàn)ather 對(duì)象的析構(gòu)會(huì)導(dǎo)致它包含的 son_ 指針被銷毀,這時(shí) Son 對(duì)象的引用計(jì)數(shù)變成 0,所以 Son 對(duì)象也會(huì)被析構(gòu)。
6.智能指針小結(jié)
我們?cè)撊绾芜x擇智能指針:
如果程序要使用多個(gè)指向同一個(gè)對(duì)象的指針,應(yīng)選擇 shared_ptr。這樣的情況包括
1.有一個(gè)指針數(shù)組,并使用一些輔助指針來(lái)標(biāo)示特定的元素,如最大的元素和最小的元素;
2.兩個(gè)對(duì)象包含都指向第三個(gè)對(duì)象的指針;
3.STL 容器包含指針。很多 STL 算法都支持復(fù)制和賦值操作,這些操作可用于 shared_ptr,但不能用于 unique_ptr(編譯器發(fā)出 warning)和 auto_ptr(行為不確定)。如果你的編譯器沒(méi)有提供 shared_ptr,可使用 Boost 庫(kù)提供的 shared_ptr。
如果程序不需要多個(gè)指向同一個(gè)對(duì)象的指針,則可使用 unique_ptr。如果函數(shù)使用 new 分配內(nèi)存,并返還指向該內(nèi)存的指針,將其返回類型聲明為 unique_ptr 是不錯(cuò)的選擇。這樣,所有權(quán)轉(zhuǎn)讓給接受返回值的 unique_ptr,而該智能指針將負(fù)責(zé)調(diào)用 delete。
參考文獻(xiàn)
https://zhuanlan.zhihu.com/p/461837602
總結(jié)
到此這篇關(guān)于c++智能指針的文章就介紹到這了,更多相關(guān)c++智能指針內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用C語(yǔ)言實(shí)現(xiàn)CRC校驗(yàn)的方法
本篇文章是對(duì)使用C語(yǔ)言實(shí)現(xiàn)CRC校驗(yàn)的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C++使用string的大數(shù)加法運(yùn)算(1)
這篇文章主要為大家詳細(xì)介紹了C++使用string的大數(shù)加法運(yùn)算,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09詳解C++的String類的字符串分割實(shí)現(xiàn)
這篇文章主要介紹了詳解C++的String類的字符串分割實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2017-07-07C++利用stringstream進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換實(shí)例
這篇文章主要介紹了C++利用stringstream進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換的方法,實(shí)例分析了使用stringstream進(jìn)行string轉(zhuǎn)int的操作技巧,需要的朋友可以參考下2015-01-01C++使用遞歸函數(shù)和棧操作逆序一個(gè)棧的算法示例
這篇文章主要介紹了C++使用遞歸函數(shù)和棧操作逆序一個(gè)棧的算法,結(jié)合實(shí)例形式分析了C++遞歸函數(shù)與逆序棧的相關(guān)操作技巧,需要的朋友可以參考下2017-05-05vscode搭建STM32開(kāi)發(fā)環(huán)境的詳細(xì)過(guò)程
這篇文章主要介紹了vscode搭建STM32開(kāi)發(fā)環(huán)境的詳細(xì)過(guò)程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05Visual?Studio2022報(bào)錯(cuò)無(wú)法打開(kāi)源文件?"openssl/conf.h"解決方法
這篇文章主要介紹了Visual?Studio2022報(bào)錯(cuò)無(wú)法打開(kāi)源文件"openssl/conf.h"解決方式,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07