make_shared的使用:
shared_ptr<string> p1 = make_shared<string>(10, '9');
shared_ptr<string> p2 = make_shared<string>("hello");
shared_ptr<string> p3 = make_shared<string>();
盡量使用make_shared初始化
C++11 中引入了智能指針, 同時(shí)還有一個(gè)模板函數(shù) std::make_shared 可以返回一個(gè)指定類型的 std::shared_ptr, 那與 std::shared_ptr 的構(gòu)造函數(shù)相比它能給我們帶來(lái)什么好處呢 ?
make_shared初始化的優(yōu)點(diǎn)
1胁出、提高性能
shared_ptr 需要維護(hù)引用計(jì)數(shù)的信息:
強(qiáng)引用, 用來(lái)記錄當(dāng)前有多少個(gè)存活的 shared_ptrs 正持有該對(duì)象. 共享的對(duì)象會(huì)在最后一個(gè)強(qiáng)引用離開的時(shí)候銷毀( 也可能釋放).
弱引用, 用來(lái)記錄當(dāng)前有多少個(gè)正在觀察該對(duì)象的 weak_ptrs. 當(dāng)最后一個(gè)弱引用離開的時(shí)候, 共享的內(nèi)部信息控制塊會(huì)被銷毀和釋放 (共享的對(duì)象也會(huì)被釋放, 如果還沒有釋放的話).
如果你通過(guò)使用原始的 new 表達(dá)式分配對(duì)象, 然后傳遞給 shared_ptr (也就是使用 shared_ptr 的構(gòu)造函數(shù)) 的話, shared_ptr 的實(shí)現(xiàn)沒有辦法選擇, 而只能單獨(dú)的分配控制塊:
如果選擇使用 make_shared 的話, 情況就會(huì)變成下面這樣:
std::make_shared(比起直接使用new)的一個(gè)特性是能提升效率辆童。使用std::make_shared允許編譯器產(chǎn)生更小,更快的代碼,產(chǎn)生的代碼使用更簡(jiǎn)潔的數(shù)據(jù)結(jié)構(gòu)。考慮下面直接使用new的代碼:
std::shared_ptr<Widget> spw(new Widget);
很明顯這段代碼需要分配內(nèi)存,但是它實(shí)際上要分配兩次。每個(gè)std::shared_ptr都指向一個(gè)控制塊暇检,控制塊包含被指向?qū)ο蟮囊糜?jì)數(shù)以及其他東西产阱。這個(gè)控制塊的內(nèi)存是在std::shared_ptr的構(gòu)造函數(shù)中分配的。因此直接使用new块仆,需要一塊內(nèi)存分配給Widget构蹬,還要一塊內(nèi)存分配給控制塊。
如果使用std::make_shared來(lái)替換
auto spw = std::make_shared<Widget>();
一次分配就足夠了悔据。這是因?yàn)閟td::make_shared申請(qǐng)一個(gè)單獨(dú)的內(nèi)存塊來(lái)同時(shí)存放Widget對(duì)象和控制塊庄敛。這個(gè)優(yōu)化減少了程序的靜態(tài)大小,因?yàn)榇a只包含一次內(nèi)存分配的調(diào)用科汗,并且這會(huì)加快代碼的執(zhí)行速度藻烤,因?yàn)閮?nèi)存只分配了一次。另外头滔,使用std::make_shared消除了一些控制塊需要記錄的信息怖亭,這樣潛在地減少了程序的總內(nèi)存占用。
對(duì)std::make_shared的效率分析可以同樣地應(yīng)用在std::allocate_shared上坤检,所以std::make_shared的性能優(yōu)點(diǎn)也可以擴(kuò)展到這個(gè)函數(shù)上兴猩。
2、 異常安全
我們?cè)谡{(diào)用processWidget的時(shí)候使用computePriority()早歇,并且用new而不是std::make_shared:
processWidget(std::shared_ptr<Widget>(new Widget), //潛在的資源泄露
computePriority());
就像注釋指示的那樣倾芝,上面的代碼會(huì)導(dǎo)致new創(chuàng)造出來(lái)的Widget發(fā)生泄露。那么到底是怎么泄露的呢箭跳?調(diào)用代碼和被調(diào)用函數(shù)都用到了std::shared_ptr晨另,并且std::shared_ptr就是被設(shè)計(jì)來(lái)阻止資源泄露的。當(dāng)最后一個(gè)指向這兒的std::shared_ptr消失時(shí)谱姓,它們會(huì)自動(dòng)銷毀它們指向的資源拯刁。如果每個(gè)人在每個(gè)地方都使用std::shared_ptr,那么這段代碼是怎么導(dǎo)致資源泄露的呢逝段?
答案和編譯器的翻譯有關(guān)垛玻,編譯器把源代碼翻譯到目標(biāo)代碼,在運(yùn)行期奶躯,函數(shù)的參數(shù)必須在函數(shù)被調(diào)用前被估值帚桩,所以在調(diào)用processWidget時(shí),下面的事情肯定發(fā)生在processWidget能開始執(zhí)行之前:
表達(dá)式“new Widget”必須被估值嘹黔,也就是账嚎,一個(gè)Widget必須被創(chuàng)建在堆上莫瞬。
std::shared_ptr(負(fù)責(zé)管理由new創(chuàng)建的指針)的構(gòu)造函數(shù)必須被執(zhí)行。
computePriority必須跑完郭蕉。
編譯器不需要必須產(chǎn)生這樣順序的代碼疼邀。但“new Widget”必須在std::shared_ptr的構(gòu)造函數(shù)被調(diào)用前執(zhí)行,因?yàn)閚ew的結(jié)構(gòu)被用為構(gòu)造函數(shù)的參數(shù)召锈,但是computePriority可能在這兩個(gè)調(diào)用前(后旁振,或很奇怪地,中間)被執(zhí)行涨岁。也就是拐袜,編譯器可能產(chǎn)生出這樣順序的代碼:
執(zhí)行“new Widget”。
執(zhí)行computePriority梢薪。
執(zhí)行std::shared_ptr的構(gòu)造函數(shù)蹬铺。
如果這樣的代碼被產(chǎn)生出來(lái),并且在運(yùn)行期秉撇,computePriority產(chǎn)生了一個(gè)異常甜攀,則在第一步動(dòng)態(tài)分配的Widget就會(huì)泄露了,因?yàn)樗肋h(yuǎn)不會(huì)被存放到在第三步才開始管理它的std::shared_ptr中琐馆。
使用std::make_shared可以避免這樣的問(wèn)題赴邻。調(diào)用代碼將看起來(lái)像這樣:
processWidget(std::make_shared<Widget>(), //沒有資源泄露
computePriority());
在運(yùn)行期,不管std::make_shared或computePriority哪一個(gè)先被調(diào)用啡捶。如果std::make_shared先被調(diào)用姥敛,則在computePriority調(diào)用前,指向動(dòng)態(tài)分配出來(lái)的Widget的原始指針能安全地被存放到被返回的std::shared_ptr中瞎暑。如果computePriority之后產(chǎn)生一個(gè)異常彤敛,std::shared_ptr的析構(gòu)函數(shù)將發(fā)現(xiàn)它持有的Widget需要被銷毀。并且如果computePriority先被調(diào)用并產(chǎn)生一個(gè)異常了赌,std::make_shared就不會(huì)被調(diào)用墨榄,因此這里就不需要考慮動(dòng)態(tài)分配的Widget了。
如果使用std::unique_ptr和std::make_unique來(lái)替換std::shared_ptr和std::make_shared勿她,事實(shí)上袄秩,會(huì)用到同樣的理由。因此逢并,使用std::make_unique代替new就和“使用std::make_shared來(lái)寫出異常安全的代碼”一樣重要之剧。
缺點(diǎn)
構(gòu)造函數(shù)是保護(hù)或私有時(shí),無(wú)法使用 make_shared
make_shared
雖好, 但也存在一些問(wèn)題, 比如, 當(dāng)我想要?jiǎng)?chuàng)建的對(duì)象沒有公有的構(gòu)造函數(shù)時(shí), make_shared
就無(wú)法使用了, 當(dāng)然我們可以使用一些小技巧來(lái)解決這個(gè)問(wèn)題, 比如這里 How do I call ::std::make_shared on a class with only protected or private constructors?
對(duì)象的內(nèi)存可能無(wú)法及時(shí)回收
make_shared
只分配一次內(nèi)存, 這看起來(lái)很好. 減少了內(nèi)存分配的開銷. 問(wèn)題來(lái)了, weak_ptr
會(huì)保持控制塊(強(qiáng)引用, 以及弱引用的信息)的生命周期, 而因此連帶著保持了對(duì)象分配的內(nèi)存, 只有最后一個(gè) weak_ptr
離開作用域時(shí), 內(nèi)存才會(huì)被釋放. 原本強(qiáng)引用減為 0 時(shí)就可以釋放的內(nèi)存, 現(xiàn)在變?yōu)榱藦?qiáng)引用, 若引用都減為 0 時(shí)才能釋放, 意外的延遲了內(nèi)存釋放的時(shí)間. 這對(duì)于內(nèi)存要求高的場(chǎng)景來(lái)說(shuō), 是一個(gè)需要注意的問(wèn)題.