智能指针知识
智能指針
智能指針的行為類似常規(guī)指針,重要的區(qū)別是它負(fù)責(zé)自動釋放所指向的對象。新標(biāo)準(zhǔn)提供的兩種智能指針的區(qū)別在于管理底層指針的方式。shared_ptr允許多個指針指向同一個對象;unique_ptr則“獨占”所指向的對象。標(biāo)準(zhǔn)庫還定義了一個名為weak_ptr的伴隨類,它是一種弱引用,指向shared_ptr所管理的對象。 這三個類型都定義在memory頭文件中。
文章目錄
- 智能指針
- shared_ptr
- shared_ptr和unique_ptr都支持的操作
- shared_ptr獨有的操作
- make_shared函數(shù)
- shared_ptr的拷貝和賦值
- shared_ptr自動銷毀所管理的對象
- shared_ptr和new結(jié)合使用
- 智能指針陷阱
- unique_ptr
- weak_ptr
shared_ptr
類似vector,智能指針是模板。當(dāng)我們創(chuàng)建一個智能指針時,必須提供額外的信息——指針可以指向的類型。
shared_ptr<string> p1; //shared_ptr,可以指向string shared_ptr<list<int>> p2; //shared_ptr,可以指向Int的list默認(rèn)初始化的智能指針中保存著一個空指針。
智能指針的使用方式與普通指針類似。解引用一個智能指針返回它指向的對象。如果在一個條件判斷中使用智能指針,效果就是檢測它是否為空。
shared_ptr和unique_ptr都支持的操作
shared_ptr<T> sp; //空智能指針,可以指向類型為T的對象 unique_ptr<T> up;p; //將p用作一個條件判斷,若p指向一個對象,則為true *p; //解引用p,獲得它所指向的對象p->mem; //等價于(*p).memp.get(); //返回p中保存的指針。要小心使用,若智能指針釋放了其對象,//返回的指針?biāo)赶虻膶ο笠簿拖Я?swap(p, q); //交換p和q中的指針 p.swap(q);shared_ptr獨有的操作
make_shared<T>(args); //返回一個shared_ptr,指向一個動態(tài)分配的類型為T的對象//使用args初始化此對象shared_ptr<T>p(q); //p是shared_ptr q的拷貝;此操作會遞增q中的計數(shù)器。q中的指針//必須能夠轉(zhuǎn)換到T*;p = q; //p和q都是shared_ptr,所保存的指針必須能夠互相轉(zhuǎn)換。//此操作會遞減p的引用次數(shù),遞增q的引用次數(shù);若p的引用次數(shù)變?yōu)?//則將其管理的原內(nèi)存釋放p.unique(); //若p.use_count()為1,返回true;否則返回false p.use_count(); //返回與p共享對象的智能指針數(shù)量;make_shared函數(shù)
最安全的分配和使用動態(tài)內(nèi)存的方法是調(diào)用一個名為make_shared的標(biāo)準(zhǔn)庫函數(shù)。此函數(shù)在動態(tài)內(nèi)存中分配一個對象并初始化它,返回指向此對象的shared_ptr。
當(dāng)要用make_shared時,必須指定想要創(chuàng)建的對象的類型。定義方式與模板類相同,在函數(shù)名之后跟一個尖括號,在其中給出類型。
shared_ptr<int> p3 = make_shared<int>(42); shared_ptr<string> p4 = make_shared<string>(10, '9'); shared_ptr<int> p5 = make_shared<int>();類似順序容器的emplace成員,make_shared用其參數(shù)來構(gòu)造給定類型的對象。例如,調(diào)用make_shared<string>時傳遞的參數(shù)必須與string的某個構(gòu)造函數(shù)相匹配,調(diào)用make_shared<int>時傳遞的參數(shù)必須能用來初始化int,依此類推,如果我們不傳遞任何參數(shù),對象就會進(jìn)行值初始化。
shared_ptr的拷貝和賦值
當(dāng)進(jìn)行拷貝或賦值操作時,每個shared_ptr都會記錄有多少個其他shared_ptr指向相同的對象。
auto p = make_shared<int>(42); //p指向的對象只有p一個引用者 auto q(p); //p和q指向相同對象,此對象有兩個引用者可以認(rèn)為每個shared_ptr都有一個關(guān)聯(lián)的計數(shù)器。通常稱為引用計數(shù),無論何時拷貝一個shared_ptr,計數(shù)器都會遞增。無論何時我們拷貝一個shared_ptr,計數(shù)器都會遞增。例如,當(dāng)用一個shared_ptr初始化另一個shared_ptr,或?qū)⑺鳛閰?shù)傳遞給一個函數(shù)以及作為函數(shù)的返回值時,它所關(guān)聯(lián)的計數(shù)器就會遞增,當(dāng)我們給shared_ptr賦予一個新值或者是shared_ptr被銷毀(例如一個局部的shared_ptr離開作用域)時,計數(shù)器就會遞減。
一旦一個shared_ptr的計數(shù)器變?yōu)?,它就會自動釋放自己所管理的對象。
shared_ptr自動銷毀所管理的對象
當(dāng)指向一個對象的最后一個shared_ptr被銷毀時,shared_ptr類會自動銷毀此對象。它是通過另一個特殊的成員函數(shù)——析構(gòu)函數(shù)完成銷毀工作的。
shared_ptr的析構(gòu)函數(shù)會遞減它所指向的對象的引用次數(shù)。如果引用計數(shù)變?yōu)?,shared_ptr的析構(gòu)函數(shù)就會銷毀對象,并釋放它所占用的內(nèi)存。
shared_ptr和new結(jié)合使用
如果我們不初始化一個智能指針,它就會被初始化為一個空指針。
shared_ptr<double> p1; //shared_ptr可以指向一個double shared_ptr<int> p2(new int(42)); //p2指向一個值為42的int接受指針參數(shù)的智能指針構(gòu)造函數(shù)是explicit的(拷貝賦值也是explicit)。因此我們不能將一個內(nèi)置指針隱式轉(zhuǎn)換為一個智能指針,必須使用直接初始化。
shared_ptr<int> p1 = new int(1024); //錯誤:必須使用直接初始化形式 shared_ptr<int> p2(new int(1024)); //正確:使用了直接初始化形式p1的初始化隱式的要求編譯器用一個new返回的int*來創(chuàng)建一個shared_ptr。由于我們不能進(jìn)行內(nèi)置指針到智能指針間的隱式轉(zhuǎn)換,因此這條初始化語句是錯誤的。出于相同的原因,一個返回shared_ptr的函數(shù)不能在其返回語句中隱式轉(zhuǎn)換一個普通指針。
shared_ptr<int> clone(int p) {return new int(p); //錯誤:隱式轉(zhuǎn)換為shared_ptr<int> }必須將shared_ptr顯式綁定到一個想要返回的指針上。
shared_ptr<int> clone(int p) {return shared_ptr<int>(new int(p)); //正確:顯式地用int*創(chuàng)建shared_ptr<int> }默認(rèn)情況下:一個用來初始化智能指針的普通指針必須指向動態(tài)內(nèi)存,因為智能指針默認(rèn)使用delete釋放它所關(guān)聯(lián)的對象。
我們可以將智能指針綁定到一個指向其他類型資源的指針上,但是為了這樣做,必須提供自己的操作來替代delete。
定義和改變shared_ptr的其他方法
shared_ptr<T> p(q); //p管理內(nèi)置指針q所指向的對象;q必須指向new分配的內(nèi)存,//且能夠轉(zhuǎn)換為T*類型shared_ptr<T> p(u); //p從unique_ptr u 那里接管了對象的所有權(quán);將u置空shared_ptr<T> p(q, d); //p接管了內(nèi)置指針q所指向的對象的所有權(quán),q必須能夠轉(zhuǎn)換為T*//類型。p將使用可調(diào)用對象d來代替deletep.reset(); //若p是唯一指向其對象的shared_ptr,reset會釋放此對象 p.reset(q); //若傳遞了可選的參數(shù)內(nèi)置指針q,會令p指向q,否則將p置為空 p.reset(q, d); //若還傳遞了參數(shù)d,將會調(diào)用d而不是delete來釋放q不要混合使用普通指針和智能指針
shared_ptr可以協(xié)調(diào)對象的析構(gòu),但這僅限于自身的拷貝(也是shared_ptr)之間。這也是為什么我們推薦使用make_shared而不是new的原因,這樣,就能在分配對象的同時就將shared_ptr與之綁定,從而避免了無意中將同一塊內(nèi)存綁定到多個獨立創(chuàng)建的shared_ptr上。
//在函數(shù)被調(diào)用時,ptr被創(chuàng)建并初始化 void process(shared_ptr<int> ptr) {//使用ptr } //ptr離開作用域,被銷毀process的參數(shù)是傳值方式傳遞的,因此實參會被拷貝到ptr中,拷貝一個shared_ptr會遞增其中的引用次數(shù),因此,在process運行過程中,引用計數(shù)值至少為2。當(dāng)process結(jié)束時,ptr的引用計數(shù)會遞減,但不會變?yōu)?。因此,當(dāng)局部變量ptr被銷毀時,ptr指向的內(nèi)存不會被釋放。
使用此函數(shù)的正確方法是傳遞給它一個shared_ptr:
shared_ptr<int> p(new int(42)); //引用計數(shù)為1 process(p); //拷貝p會遞增它的引用次數(shù),在process中引用次數(shù)為2 int i = *p; //正確,引用次數(shù)為1雖然不能傳遞給process一個內(nèi)置指針,但是可以傳遞給它一個(臨時的)shared_ptr,這個shared_ptr是用一個內(nèi)置指針顯式構(gòu)造的。但是,這樣做很可能會導(dǎo)致錯誤:
int *x(new int(1024)); //危險:x是一個普通指針,不是一個智能指針 process(x); //錯誤。不能將int*轉(zhuǎn)換為shared_ptr<int> process(shared_ptr<int>(x)); //合法的,但內(nèi)存會被釋放 int j = *x; //為定義的,x是一個空懸指針在上面的調(diào)用中,我們將一個臨時的shared_ptr傳遞給process。當(dāng)這個調(diào)用所在的表達(dá)式結(jié)束時,這個臨時對象就被銷毀了。銷毀這個臨時變量會遞減引用計數(shù),此時引用計數(shù)就變?yōu)?了。因此,當(dāng)臨時對象被銷毀時,它所指向的內(nèi)存會被釋放。
但是x繼續(xù)指向(已經(jīng)釋放的)內(nèi)存,從而變成一個空懸指針。如果試圖使用x的值,其行為是為定義的。
當(dāng)將一個shared_ptr綁定到一個普通指針時,我們就將內(nèi)存的管理責(zé)任交給了這個shared_ptr。一旦這樣做了,我們就不應(yīng)該再使用內(nèi)置指針來訪問shared_ptr所指向的內(nèi)存。
不要使用get初始化另一個智能指針或為智能指針賦值
智能指針類型定義了一個名為get的函數(shù),它返回一個內(nèi)置指針,指向智能指針管理的對象。但我們需要向不能使用智能指針的代碼傳遞一個內(nèi)置指針時,使用get返回的指針的代碼不能delete此指針。
不能將get返回的指針綁定到智能指針上,這是錯誤的。
shared_ptr<int> p(new int(42)); //引用計數(shù)為1 int* q = p.get(); //正確:但使用q時要注意,不要讓它管理的指針被釋放 {//新程序塊shared_ptr<int>(q); //兩個獨立的shared_ptr指向相同的內(nèi)存 } //程序塊結(jié)束,q被銷毀,它指向的內(nèi)存被釋放 int foo = *p; //未定義:p指向的內(nèi)存已經(jīng)被釋放了p和q指向相同的內(nèi)存。由于它們是相互獨立創(chuàng)建的,因此各自的引用計數(shù)都是1。當(dāng)q所在的程序塊結(jié)束時,q被銷毀,這回導(dǎo)致q指向的內(nèi)存被釋放。從而p變成一個空懸指針,意味著當(dāng)我們試圖使用p時,將發(fā)生未定義的行為。而且,當(dāng)p被銷毀時,這塊內(nèi)存會被第二次delete。
智能指針陷阱
- 不使用相同的內(nèi)置指針值初始化(或reset)多個智能指針
- 不delete get()返回的指針
- 不使用get()初始化或reset另一個智能指針
- 如果你使用了get()返回的指針,記住當(dāng)最后一個對應(yīng)的智能指針銷毀時,你的指針就變?yōu)闊o效了。
- 如果你使用智能指針管理的資源不是new分配的內(nèi)存,記住傳遞給它一個刪除器。
unique_ptr
一個unique_ptr擁有它所指向的對象。與shared_ptr不同,某個時刻只能有一個unique_ptr指向一個給定對象。當(dāng)unique_ptr被銷毀時,它所指向的對象也被銷毀。
與shared_ptr不同,沒有類似make_shared的標(biāo)準(zhǔn)庫函數(shù)返回一個unique_ptr。當(dāng)我們定義一個unique_ptr時,需要將其綁定到一個new返回的指針上,初始化unique_ptr必須采用直接初始化形式。
unique_ptr<double> p1; //可以指向一個double的unique_ptr unique_ptr<int> p2(new int(42)); //p2指向一個值為42的int由于一個unique_ptr擁有它指向的對象,因此unique_ptr不支持普通的拷貝或賦值操作:
unique_ptr<string> p1(new string("Stegosaurus")); unique_ptr<string> p2(p1); //錯誤:unique_ptr不支持拷貝 unique_ptr<string> p3; p3 = p2; //錯誤:unique_ptr不支持賦值unique_ptr操作
unique_ptr<T> u1; //空unique_ptr,可以指向類型為T的對象。u1會使用delete unique_ptr<T, D> u2; //來釋放它的指針;u2會使用一個類型為D的可調(diào)用對象來釋放//它的指針unique_ptr<T, D> u(d); //空unique_ptr,指向類型為T的對象,用類型為D的對象d代替//deleteu.release(); //u放棄對指針的控制權(quán),返回指針,并將u置為空u.reset(); //釋放u指向的對象 u.reset(q); //如果提供了內(nèi)置指針q,令u指向這個對象,否則將u置為空 u.reset(nullptr);雖然我們不能拷貝或賦值unique_ptr,但是可以通過調(diào)用release或reset將指針的所有權(quán)從一個(非const)unique_ptr轉(zhuǎn)移給另一個unique:
unique_ptr<string> p2(p1.release()); //將所有權(quán)從p1轉(zhuǎn)給p2 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成員接受一個可選的指針參數(shù),令unique_ptr重新指向給定的指針。如果unique_ptr不為空,它原來指向的對象被釋放。因此,對p2調(diào)用reset釋放了初始化string所使用的內(nèi)存,將p3對指針的所有權(quán)轉(zhuǎn)移給p2,并將p3置為空。
調(diào)用release會切斷unique_ptr和它原來管理的對象間的聯(lián)系。release返回的指針通常被用來初始化另一個智能指針或給另一個智能指針賦值。如果我們不用另一個智能指針來保存release返回的指針,我們的程序就要負(fù)責(zé)資源的釋放。
p2.release(); //錯誤:p2不會釋放內(nèi)存,而且我們丟失了指針 auto p = p2.release(); //正確,但我們必須記得delete(p)不能拷貝unique_ptr的規(guī)則有一個例外:我們可以拷貝或賦值一個將要被銷毀的unique_ptr。
最常見的例子是從函數(shù)返回一個unique_ptr:
unique_ptr<int> clone(int p) {//正確,從int*創(chuàng)建一個unique_ptr<int>return unique_ptr<int>(new int(p)); }還可以返回一個局部對象的拷貝:
unique_ptr<int> clone(int p) {unique_ptr<int> ret(new int(p));return ret; }借助std::move()可以實現(xiàn)將一個unique_ptr對象賦值給另一個unique_ptr對象,其目的是實現(xiàn)所有權(quán)的轉(zhuǎn)移。
//A作為一個類 std::unique_ptr<A> ptr1(new A()); std::unique_ptr<A> ptr2 = std::move(ptr1);weak_ptr
weak_ptr是一種不控制所指向?qū)ο笊嫫诘闹悄苤羔槨K赶蛴梢粋€shared_ptr管理的對象。將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用次數(shù)。一旦最后一個指向?qū)ο蟮膕hared_ptr被撤銷,對象就會被釋放。即使有weak_ptr指向?qū)ο?#xff0c;對象也還是會被釋放。
weak_ptr
weak_ptr<T> w; //空weak_ptr可以指向類型為T的對象weak_ptr<T> w(sp); //與shared_ptr sp 指向相同對象的weak_ptr。T必須//能夠轉(zhuǎn)換為sp指向的類型w = p; //p可以是一個shared_ptr或一個weak_ptr。賦值后//w與p共享對象w.reset(); //將w置空 w.use_count(); //與w共享對象的shared_ptr的數(shù)量 w.expired(); //若w.use_count()為0,返回true,否則返回false w.lock(); //如果expired為true,返回一個空shared_ptr;否則返回一個//指向w的對象的shared_ptr當(dāng)我們創(chuàng)建一個weak_ptr時,要用一個shared_ptr來初始化它
auto p = make_shared<int>(42); weak_ptr<int> wp(p); //wp弱共享p;p的引用計數(shù)未改變本例中,wp和p指向相同的對象。由于是弱引用,創(chuàng)建wp不會改變p的引用計數(shù);wp指向的對象可能被釋放掉。
由于對象可能不存在,我們不能使用weak_ptr直接訪問對象,而必須調(diào)用lock。此函數(shù)檢查weak_ptr指向的對象是否仍存在。如果存在,lock返回一個指向共享對象的shared_ptr。
由于weak_ptr并沒有重載operator->和operator *操作符,因此不可以直接通過weak_ptr使用對象,同時也沒有提供get()函數(shù)直接獲取裸指針。典型的用法是調(diào)用其lock函數(shù)來獲得shared_ptr示例,進(jìn)而訪問原始對象。
初始化方式
- 通過shared_ptr直接構(gòu)造,也可以通過隱式轉(zhuǎn)換來構(gòu)造;
- 允許移動構(gòu)造,也允許拷貝構(gòu)造
總結(jié)
- 上一篇: 像素大厨可以导出html标注吗,前端标注
- 下一篇: 几款好用的免费内网穿透