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