C++Smart Pointer 智能指針詳解
一、為啥使用智能指針呢
標(biāo)準(zhǔn)庫中的智能指針: std::auto_ptr --single ownership (C++98中出現(xiàn),缺陷較多,被摒棄) std::unique_ptr --single ownership (C++11替代std::auto_ptr,用于單線程) std::shared_ptr --shared ownership (C++11,用于多線程) std::weak_ptr --temp/no ownership (C++11) Introduced in C++ 11 Defined in <memory> header.
首先看一個(gè)下面的栗子,左邊是木有使用智能指針的情況,當(dāng)執(zhí)行foo()
函數(shù),其中的e
指針會(huì)在bar(e)
時(shí)傳入bar
函數(shù),但是在bar
函數(shù)結(jié)束后沒有人為delete e
時(shí),就會(huì)導(dǎo)致內(nèi)存泄漏;但是在右邊的栗子中,使用了unique_ptr
智能指針(single ownership),就能防止內(nèi)存泄漏。
智能指針主要用于管理在堆上分配的內(nèi)存,它將普通的指針封裝為一個(gè)棧對(duì)象。當(dāng)棧對(duì)象的生存周期結(jié)束后,會(huì)在析構(gòu)函數(shù)中釋放掉申請(qǐng)的內(nèi)存,從而防止內(nèi)存泄漏。
auto_ptr
智能指針:(C++11出來前只有這種智能指針)當(dāng)對(duì)象拷貝或者賦值后,前面的對(duì)象就懸空了。unique_ptr
智能指針:防止智能指針拷貝和復(fù)制。shared_ptr
智能指針:通過引用計(jì)數(shù)的方式來實(shí)現(xiàn)多個(gè)shared_ptr
對(duì)象之間共享資源。weak_ptr
智能指針:可以從一個(gè)shared_ptr
或另一個(gè)weak_ptr
對(duì)象構(gòu)造,它的構(gòu)造和析構(gòu)不會(huì)引起引用記數(shù)的增加或減少。
注意:每一種智能指針都可以增加內(nèi)存的引用計(jì)數(shù)。
- 智能指針分為兩類:
- 一種是可以使用多個(gè)智能指針管理同一塊內(nèi)存區(qū)域,每增加一個(gè)智能指針,就會(huì)增加1次引用計(jì)數(shù),
- 另一類是不能使用多個(gè)智能指針管理同一塊內(nèi)存區(qū)域,通俗來說,當(dāng)智能指針2來管理這一塊內(nèi)存時(shí),原先管理這一塊內(nèi)存的智能指針1只能釋放對(duì)這一塊指針的所有權(quán)(ownership)。
- 按照這個(gè)分類標(biāo)準(zhǔn),
auto_ptr
unique_ptr
weak_ptr
屬于后者,shared_ptr
屬于前者。
對(duì)shared_ptr
進(jìn)行初始化時(shí)不能將一個(gè)普通指針直接賦值給智能指針,因?yàn)橐粋€(gè)是指針,一個(gè)是類??梢酝ㄟ^make_shared
函數(shù)或者通過構(gòu)造函數(shù)傳入普通指針。并可以通過get
函數(shù)獲得普通指針。
#include <string> #include <memory> using namespace std; class report { private: string str; public: report(const string s):str(s) //構(gòu)造方法 { cout<<"1 report Object has been build!"<<endl; } ~report() { cout<<"3 report Object deleted!"<<endl; } void talk() { cout<<str<<endl; } }; int main() { string talk="2 hello,this is a test!"; { auto_ptr<report> ptr(new report(talk)); ptr->talk(); } { shared_ptr<report> ptr(new report(talk)); ptr->talk(); } { unique_ptr<report> ptr(new report(talk)); ptr->talk(); } return 0; }
二、shared_ptr智能指針
shared_ptr
實(shí)現(xiàn)了共享擁有的概念,利用“引用計(jì)數(shù)”來控制堆上對(duì)象的生命周期。
share_ptr
的生命周期:
原理:在初始化的時(shí)候引用計(jì)數(shù)設(shè)為1,每當(dāng)被拷貝或者賦值的時(shí)候引用計(jì)數(shù)+1,析構(gòu)的時(shí)候引用計(jì)數(shù)-1,直到引用計(jì)數(shù)被減到0,那么就可以delete掉對(duì)象的指針了。他的構(gòu)造方式主要有以下三種:
shared_ptr<Object> ptr; shared_ptr<Object> ptr(new Object); shared_ptr<Object> ptr(new Object, [=](Object *){ //回收資源時(shí)調(diào)用的函數(shù) }); auto ptr = make_shared<Object>(args);
- 第一種空構(gòu)造,沒有指定
shared_ptr
管理的堆上對(duì)象的指針,所以引用計(jì)數(shù)為0,后期可以通過reset()
成員函數(shù)來指定其管理的堆上對(duì)象的指針,reset()
之后引用計(jì)數(shù)設(shè)為1。 - 第二種是比較常見的構(gòu)造方式,構(gòu)造函數(shù)里面可以放堆上對(duì)象的指針,也可以放其他的智能指針(如
weak_ptr
)。 - 第三種構(gòu)造方式指定了
shared_ptr
在析構(gòu)自己所保存的堆上對(duì)象的指針時(shí)(即引用計(jì)數(shù)為0時(shí))所要調(diào)用的函數(shù),這說明我們可以自定義特定對(duì)象的特定析構(gòu)方式。同樣的,reset()
成員函數(shù)也可以指定析構(gòu)時(shí)調(diào)用的指定函數(shù)。 - 第四種方法:較常見,構(gòu)造
shared_ptr
的方式(最安全):
auto ptr = make_shared<Object>(args);
上面第四種方法,使用標(biāo)準(zhǔn)庫里邊的
make_shared<>()
模板函數(shù)。該函數(shù)會(huì)調(diào)用模板類的構(gòu)造方法,實(shí)例化一個(gè)堆上對(duì)象,然后將保存了該對(duì)象指針的shared_ptr
返回。參數(shù)是該類構(gòu)造函數(shù)的參數(shù),所以使用make_shared<>()
就好像單純地在構(gòu)造該類對(duì)象一樣。auto
是C++11的一個(gè)關(guān)鍵字,可以在編譯期間自動(dòng)推算變量的類型,在這里就是shared_ptr<Object>
類型。
shared_ptr
的其他成員函數(shù):
use_count() //返回引用計(jì)數(shù)的個(gè)數(shù) unique() //返回是否是獨(dú)占所有權(quán)(use_count是否為1) swap() //交換兩個(gè)shared_ptr對(duì)象(即交換所擁有的對(duì)象,引用計(jì)數(shù)也隨之交換) reset() //放棄內(nèi)部對(duì)象的所有權(quán)或擁有對(duì)象的變更, 會(huì)引起原有對(duì)象的引用計(jì)數(shù)的減少
三、unique_ptr智能指針
注意unique_ptr
是single ownership的,不能拷貝。其構(gòu)造方式如下:
unique_ptr
的生命周期:
四、weak_ptr智能指針
五、智能指針怎么解決交叉引用,造成的內(nèi)存泄漏
結(jié)論:創(chuàng)建對(duì)象時(shí)使用shared_ptr
強(qiáng)智能指針指向,其余情況都使用weak_ptr
弱智能指針指向。
5.1 交叉引用的栗子:
當(dāng)A類中有一個(gè)指向B類的shared_ptr
強(qiáng)類型智能指針,B類中也有一個(gè)指向A類的shared_ptr
強(qiáng)類型智能指針。
main
函數(shù)執(zhí)行后有兩個(gè)強(qiáng)智能指針指向了對(duì)象A,對(duì)象A的引用計(jì)數(shù)為2,B類也是:
#include <iostream> #include <memory> using namespace std; class B; class A{ public: shared_ptr<B> _bptr; }; class B{ public: shared_ptr<A> _aptr; }; int main(){ shared_ptr<A> aptr(new A()); shared_ptr<B> bptr(new B()); aptr->_bptr = bptr; bptr->_aptr = aptr; return 0; }
而當(dāng)主函數(shù)main
的return
返回后,對(duì)象A
的引用計(jì)數(shù)減一變?yōu)?(aptr
沒指向A
對(duì)象了),B
對(duì)象也是,引用計(jì)數(shù)不為0,即不能析構(gòu)2個(gè)對(duì)象釋放內(nèi)存,造成內(nèi)存泄漏。
5.2 解決方案
將類A和類B中的shared_ptr
強(qiáng)智能指針都換成weak_ptr
弱智能指針;
class A{ public: weak_ptr<B> _bptr; }; class B{ public: weak_ptr<A> _aptr; };
weak_ptr
弱智能指針,雖然有引用計(jì)數(shù),但實(shí)際上它并不增加計(jì)數(shù),而是只觀察對(duì)象的引用計(jì)數(shù)。所以此時(shí)對(duì)象A的引用計(jì)數(shù)只為1,對(duì)象B的引用計(jì)數(shù)也只為1。
六、智能指針的注意事項(xiàng)
- 避免同一塊內(nèi)存綁定到多個(gè)獨(dú)立創(chuàng)建的shared_ptr上,因此要不使用相同的內(nèi)置指針初始化(或reset)多個(gè)智能指針,不要混合使用智能指針和普通指針,堅(jiān)持只用智能指針。
- 不delete get() 函數(shù)返回的指針,因?yàn)檫@樣操作后,
shared_ptr
并不知道它管理的內(nèi)存被釋放了,會(huì)造成shared_ptr重復(fù)析構(gòu)。 - 不使用 get()函數(shù)初始化或(reset)另外的智能指針。
shared_ptr<int> p = make_share<int> (42); int *q = p.get(); { shared_ptr<int>(q); } // 程序塊結(jié)束,q被銷毀,指向的內(nèi)存被釋放。 int foo = *p; // 出錯(cuò),p指向的內(nèi)存已經(jīng)被q釋放,這是用get() 初始化另外的智能指針惹得禍。 // 請(qǐng)記住,永遠(yuǎn)不要用get初始化另外一個(gè)智能指針。
能使用unique_ptr
時(shí)就不要使用share_ptr
指針(后者需要保證線程安全,所以在賦值or銷毀時(shí)overhead開銷更高)。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
詳解C++編程中多級(jí)派生時(shí)的構(gòu)造函數(shù)和訪問屬性
這篇文章主要介紹了詳解C++編程中多級(jí)派生時(shí)的構(gòu)造函數(shù)和訪問屬性,是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09C語言實(shí)現(xiàn)字符串字符反向排列的方法詳解
這篇文章主要為大家分享了幾種通過C語言實(shí)現(xiàn)字符串字符反向排列(不是逆序打印)的方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-05-05C++內(nèi)存管理之簡易內(nèi)存池的實(shí)現(xiàn)
大家好,本篇文章主要講的是C++內(nèi)存管理之簡易內(nèi)存池的實(shí)現(xiàn),感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下2021-12-12C++ 如何判斷四個(gè)點(diǎn)是否構(gòu)成正方形
這篇文章主要介紹了C++ 如何判斷四個(gè)點(diǎn)是否構(gòu)成正方形的案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-03-03關(guān)于C語言函數(shù)strstr()的分析以及實(shí)現(xiàn)
以下是對(duì)C語言中strstr()函數(shù)的使用進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以參考下2013-07-07