std::make_unique<T>和std::make_shared<T>
更建議使用:std::make_unique<T>構(gòu)造unique_ptr對(duì)象;std::make_shared<T>構(gòu)造shared_ptr對(duì)象
?
std::make_shared是C++11的一部分,std::make_unique不是,它在C++14才納入標(biāo)準(zhǔn)庫(kù)。如果你使用的是C++11,不用憂傷,因?yàn)閟td::make_unique的簡(jiǎn)單版本很容易寫出來:
template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&&... params)
{
return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}
make_unique只是把參數(shù)完美轉(zhuǎn)發(fā)給要?jiǎng)?chuàng)建對(duì)象的構(gòu)造函數(shù),再?gòu)膎ew出來的原生指針構(gòu)造std::unique_ptr。這種形式的函數(shù)不支持?jǐn)?shù)組和自定義刪除器。
三個(gè)make函數(shù):std::make_unique、std::make_shared、std::allocate_shared,make函數(shù):把任意集合的參數(shù)完美轉(zhuǎn)發(fā)給動(dòng)態(tài)分配對(duì)象的構(gòu)造函數(shù),然后返回一個(gè)指向那對(duì)象的智能指針。std::allocate_shared,它與std::make_shared類似,除了它第一個(gè)參數(shù)是個(gè)分配器,指定動(dòng)態(tài)分配對(duì)象的方式。
使用make函數(shù)更可取的第一個(gè)原因。考慮以下:
auto upw1(std::make_unique<Widget>()); // 使用make函數(shù)
std::unique_ptr<Widget> upw2(new Widget); // 不使用make函數(shù)
auto spw1(std::make_shared<Widget>()); // 使用make函數(shù)
std::shared_ptr<Widget> spw2(new Widget); // 不使用make函數(shù)
它們本質(zhì)上的不同是:使用new的版本重復(fù)著需要?jiǎng)?chuàng)建的類型(即出現(xiàn)了兩次Widget),而使用make函數(shù)不需要。
第二個(gè)原因異常安全。
void processWidget(std::shared_ptr<Widget> spw, int priority);計(jì)算優(yōu)先級(jí)的函數(shù),
int computePriority();processWidget(std::shared_ptr<Widget>(new Widget), computePriority()); // 可能會(huì)資源泄漏
這代碼中new出來的Widget可能會(huì)泄漏,為什么?
調(diào)用processWidget時(shí),下面的事會(huì)在processWidget開始前執(zhí)行:
- “new Widget”。
 - std::shared_ptr構(gòu)造函數(shù)執(zhí)行。
 - computePriority運(yùn)行。
 
編譯器在生成代碼時(shí)不會(huì)保證上面的執(zhí)行順序,“new Widget”一定會(huì)在std::shared_ptr構(gòu)造函數(shù)之前執(zhí)行,但是computePriority可能在它們之前就被調(diào)用了,可能在它們之后,可能在它們之間。所以,編譯器生成代碼的執(zhí)行順序有可能是這樣的:
如果生成的代碼真的是這樣,那么在運(yùn)行時(shí),computePriority產(chǎn)生了異常,步驟1中動(dòng)態(tài)分配的Widget就泄漏了
使用std::make_shared可以避免這問題。
processWidget(std::make_shared<Widget>(), computePriority())std::make_shared的一個(gè)特點(diǎn)(相比于直接使用new)是提高效率。使用std::make_shared允許編譯器生成更小、更快的代碼。考慮當(dāng)我們直接使用new時(shí):
std::shared_ptr<Widget> spw(new Widget);很明顯這代碼涉及一次內(nèi)存分配,不過,它實(shí)際上分配兩次。每個(gè)std::shared_ptr內(nèi)都含有一個(gè)指向控制塊的指針,這控制塊的內(nèi)存是由std::shared_ptr的構(gòu)造函數(shù)分配的,那么直接使用new,需要為Widget分配一次內(nèi)存,還需要為控制塊分配一次內(nèi)存。
如果用std::make_shared呢,
auto spw = std::make_shared<Widget>();一次分配就夠了,因?yàn)閟td::make_shared會(huì)分配一大塊內(nèi)存來同時(shí)持有Widget對(duì)象和控制塊。這種優(yōu)化減少了程序的靜態(tài)尺寸,因?yàn)榇a只需要調(diào)用一次內(nèi)存分配函數(shù),增加了代碼執(zhí)行的速度,因?yàn)橹恍枰峙湟淮蝺?nèi)存。而且,使用std::make_shared能避免一些控制塊的信息,潛在地減少了程序占用的內(nèi)存空間。
但std::unique_ptr和std::shared_ptr可以指定刪除器,make函數(shù)不可以,
auto widgetDeleter = [](Widget* pw) {...}我們可以直接使用new創(chuàng)建智能指針:
?std::unique_ptr<Widget, decltype(widgetDeleter)> upw(new Widget, widgetDeleter);
std::shared_ptr<Widget> spw(new Widget, widgetDeleter);
make函數(shù)的第二個(gè)限制。當(dāng)創(chuàng)建一個(gè)對(duì)象時(shí),如果該對(duì)象的重載構(gòu)造函數(shù)帶有std::initializer_list參數(shù),那么使用大括號(hào)創(chuàng)建對(duì)象會(huì)偏向于使用帶std::initializer_list構(gòu)造,要使用圓括號(hào)創(chuàng)建對(duì)象才能使用到非std::initializer_list構(gòu)造。make函數(shù)把它們的參數(shù)完美轉(zhuǎn)發(fā)給對(duì)象的構(gòu)造函數(shù),那么它們用的是大括號(hào)還是圓括號(hào)呢?
?auto upv = std::make_unique<std::vector<int>>(10, 20);
auto spv = std::make_shared<std::vector<int>>(10, 20);
上面兩個(gè)都創(chuàng)建內(nèi)含10個(gè)值為20的std::vector。make函數(shù)內(nèi),完美轉(zhuǎn)發(fā)使用的是圓括號(hào),而不是大括號(hào)。壞消息是如果你想用大括號(hào)初始化來構(gòu)造指向的對(duì)象,你只能直接使用new,如果你想使用make函數(shù),就要求完美轉(zhuǎn)發(fā)的能力支持大括號(hào)初始化,但是大括號(hào)初始化不能被完美轉(zhuǎn)發(fā)。不過也有一種能工作的方法:用auto推斷大括號(hào),從而創(chuàng)建一個(gè)std::initializer_list對(duì)象,然后把a(bǔ)uto變量傳遞給make函數(shù):
?// 創(chuàng)建 std::initializer_list
auto initList = {10, 20};
// 使用std::initializer_list構(gòu)造函數(shù)創(chuàng)建std::vector,容器中只有兩個(gè)元素
auto spv = std::make_shared<std::vector<int>>(initList);
對(duì)于std::unique_ptr,只有兩種情況(自定義刪除器和大括號(hào)初始化)會(huì)讓它的make函數(shù)出問題。對(duì)于std::shared_ptr和它的make函數(shù),就多兩種情況,這兩種情況都是邊緣情況,不過一些開發(fā)者就喜歡住在邊緣。
一些類定義了自己的operator new和operator delete函數(shù),這些函數(shù)的出現(xiàn)暗示著常規(guī)的全局內(nèi)存分配和回收不適合這種類型的對(duì)象。通常情況下,設(shè)計(jì)這些函數(shù)只有為了精確分配和銷毀對(duì)象。這兩個(gè)函數(shù)不適合std::shared_ptr的自定義分配(借助std::allocate_shared)和回收(借助自定義刪除器),因?yàn)閟td::allocate_shared請(qǐng)求內(nèi)存的大小不是對(duì)象的尺寸,而是對(duì)象尺寸加上控制塊尺寸。結(jié)果就是,使用make函數(shù)為那些定義自己版本的operator new和operator delete的類創(chuàng)建對(duì)象是糟糕的。
比起直接使用new,std::make_shared的占用內(nèi)存大小和速度優(yōu)勢(shì)來源于:std::shared_ptr的控制塊與它管理的對(duì)象放在同一塊內(nèi)存。當(dāng)引用計(jì)數(shù)為0時(shí),對(duì)象被銷毀(即調(diào)用了析構(gòu)函數(shù)),但是,它使用的內(nèi)存不會(huì)釋放,除非控制塊也被銷毀,因?yàn)閷?duì)象和控制塊在同一塊動(dòng)態(tài)分配的內(nèi)存上。
控制塊上除了引用計(jì)數(shù)還有別的信息。引用計(jì)數(shù)記錄的是有多少std::shared_ptr指向控制塊,但是控制塊還有第二種引用計(jì)數(shù),記錄有多少std::weak_ptr指向控制塊。這種引用計(jì)數(shù)稱為weak count。當(dāng)std::weak_ptr檢查它是否過期時(shí)(expired),它通過檢查控制塊中的引用計(jì)數(shù)(不是weak count)來實(shí)現(xiàn)。如果引用計(jì)數(shù)為0,std::weak_ptr就過期,否則就沒有過期。
但是,只要有std::weak_ptr指向控制塊(weak count大于0),控制塊就必須繼續(xù)存在,而只要控制塊存在,容納它的內(nèi)存塊也依舊存在。那么,通過make函數(shù)創(chuàng)建對(duì)象分配的內(nèi)存,要直到最后一個(gè)指向它的std::shared_ptr和std::weak_ptr對(duì)象銷毀,才能被回收。
如果對(duì)象的類型非常大,并且最后一個(gè)std::shared_ptr銷毀和最后一個(gè)std::weak_ptr銷毀之間的時(shí)間間隔很大,那么對(duì)象銷毀和內(nèi)存被回收之間的會(huì)有延遲
如果直接使用new,ReallyBigType對(duì)象的內(nèi)存只要最后一個(gè)std::shared_ptr被銷毀就能被釋放。
有個(gè)小小的性能問題,在異常不安全的調(diào)用中,我們傳給processWidget的是一個(gè)右值
?processWidget(
std::shared_ptr<Widget>(new Widget, cusDel), // 參數(shù)是右值
computePriority()
);
但是在異常安全的調(diào)用中,我們傳遞的是個(gè)左值:
processWidget(spw, computePriority()); // 參數(shù)是左值因?yàn)閜rocessWidget的std::shared_ptr參數(shù)是值傳遞,從一個(gè)右值構(gòu)造使用的是移動(dòng),從一個(gè)左值構(gòu)造使用的是拷貝。對(duì)于std::shared_ptr,這差別挺大的,因?yàn)榭截愐粋€(gè)std::shared_ptr需要增加它的引用計(jì)數(shù),而移動(dòng)操作完全不用操作引用計(jì)數(shù)。
使用std::move來把spw轉(zhuǎn)化為右值:
processWidget(std::move(spw), computePriority()); // 現(xiàn)在也一樣高效這是有趣的而且值得知道,但是通常也是不相干的,因?yàn)槟愫苌儆欣碛刹挥胢ake函數(shù),除非你有迫不得已的理由,否則,你應(yīng)該使用make函數(shù)。
總結(jié)
需要記住的3點(diǎn):
- 相比于直接使用new,make函數(shù)可以消除代碼重復(fù),提高異常安全,而且std::make_shared和std::allocate_shared生成的代碼更小更快。
 - 不適合使用make函數(shù)的場(chǎng)合包括需要指定自定義刪除器和想要傳遞大括號(hào)初始值。
 - 對(duì)于std::shared_ptr,使用make函數(shù)可能是不明智的場(chǎng)合包括(1)自定義內(nèi)存管理函數(shù)的類、(2)內(nèi)存緊張的系統(tǒng)中,有非常大的對(duì)象,然后std::weak_ptr比std::shared_ptr長(zhǎng)壽。
 
總結(jié)
以上是生活随笔為你收集整理的std::make_unique<T>和std::make_shared<T>的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 一个单向链表,输出该链表中倒数第k个结点
 - 下一篇: 螺旋模型(Spiral Model)