一. make系列函數ios
(一)三個make函數編程
1. std::make_shared:用於建立shared_ptr。GCC編譯器中,其內部是經過調用std::allocate_shared來實現的。安全
2. std::make_unique:C++14中加入標準庫。數據結構
3. std::allocate_shared:行爲和std::make_shared同樣,只不過第1個實參是個用以動態分配內存的分配器對象。ide
//make_unique的模擬實現 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_shared的實現(GCC編譯器) template<typename _Tp, typename... _Args> inline shared_ptr<_Tp> make_shared(_Args&&... __args) { typedef typename std::remove_const<_Tp>::type _Tp_nc; return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(), std::forward<_Args>(__args)...); }
(二)與new相比,make系列函數的優點函數
1. 避免代碼冗餘:建立智能指針時,被建立對象的類型只需寫1次。如make_shared<T>(),而用new建立智能指針時,須要寫2次。性能
2. 異常安全:make系列函數可編寫異常安全代碼,改進了new的異常安全性。spa
3. 提高性能:編譯器有機會利用更簡潔的數據結構產生更小更快的代碼。使用make_shared<T>時會一次性進行內存分配,該內存單塊(single chunck)既保存了T對象又保存與其相關聯的控制塊。而直接使用new表達式,除了爲T分配一次內存,還要爲與其關聯的控制塊再進行一次內存分配。 指針
二. make系列函數的侷限code
(一)全部的make系列函數都不容許自定義刪除器。
(二)make系列函數建立對象時,不能接受{}初始化列表。(這是由於完美轉發的轉發函數是個模板函數,它利用模板類型進行推導。所以沒法將「{}」推導爲initializer_list,具體見《完美轉發》一課)。換言之,make系列只能將圓括號內的形參完美轉發。
(三)自定義內存管理的類(如重載了operator new 和operator delete),不建議使用make_shared來建立。緣由以下:
1. 重載operator new和operator delete時,每每用來分配和釋放該類精確尺寸(sizeof(T))的內存塊。
2. 而make_shared建立的shared_ptr,是一個自定義了分配器(std::allocate_shared)和刪除器的智能指針,由allocate_shared分配的內存大小也不等於上述的尺寸,而是在此基礎上加上控制塊的大小。
3. 所以,不建議使用make函數爲那些重載了operator new和operator delete的類建立對象。
(四)對象的內存可能沒法及時回收
1. make_shared 只分配一次內存,減小了內存分配的開銷。使得控制塊和託管對象在同一內存塊上分配。而控制塊是由shared_ptr和weak_ptr共享的,所以二者共同管理着這個內存塊(託管對象+控制塊)。
2. 當強引用計數爲0時,託管對象被析構(即析構函數被調用),但內存塊並未被回收,只有等到最後一個weak_ptr離開做用域時,弱引用也減爲0纔會釋放這塊內存塊。本來強引用減爲0時就能夠釋放的內存, 如今變爲了強引用和弱引用都減爲0時才能釋放, 意外的延遲了內存釋放的時間。這對於內存要求高的場景來講, 是一個須要注意的問題。
3.所以,當內存緊張且託管對象很是大時,若是weak_ptr的生命期比shared_ptr更長時,不建議使用make_shared。
【編程實驗】make系列函數的優劣
#include <iostream> #include <memory> //for smart pointer #include <vector> using namespace std; class Widget { public: Widget(){} Widget(int x, int y){ cout << "Widget(int x, int y)" << endl; } Widget(const std::initializer_list<int> li) { cout << "Widget(std::initializer_list<int> li)"<< endl; } }; void processWidget(std::shared_ptr<Widget> spw, int priority){} int computePriority() { /*throw 1;*/ return 0; }//假設該函數會拋出異常 class ReallyBigType {};//大對象 int main() { //1. make系列函數的優點 //1.1 避免代碼冗餘,減小重複書寫類型 auto upw1(std::make_unique<Widget>()); //使用make系列函數,Widget只需寫一次 std::unique_ptr<Widget> upw2(new Widget); //使用new,Widget需寫二次。 //1.2 make系統異常安全性更高 //在將實參傳遞processWidget前,各個參數時必須先被計算出來,假設順序以下(因編譯器和調用約定而異) //A. 先new Widget,即一個Widget對象在堆上建立。 //B. 執行computePriority,但假設此時該函數產生異常,那上面的堆對象就會泄漏。 //C. 正常流程下,應執行shared_ptr構造函數,但因爲第2步的異常,使得第1步分配的堆對象永遠不會被這個 // shared_ptr接管(實際上該shared_ptr本身都沒有機會建立),因而資源泄漏! processWidget(shared_ptr<Widget>(new Widget), computePriority());//潛在資源泄漏! //異常安全! processWidget(make_shared<Widget>(), computePriority()); //若是make_shared首先被調用當computePriority //發生異常時,則以前的shared_ptr會被釋放, //從而釋放Widget對象。若是computePriority先 //調用,則make_shared沒有機會被調用,也就不會 //有資源泄漏! //1.3 make_shared一次性分配內存 auto spw1 = std::make_shared<Widget>(); //一次性分配一個內存單塊,可容納Widget對象和控制塊內存 std::shared_ptr<Widget> spw2(new Widget); //2次分配:new和分配控制塊各一次。 //2. make系列函數的侷限性 //2.1 make不能自定義刪除器 auto widgetDeleter = [](Widget* pw) {delete pw; }; std::unique_ptr<Widget, decltype(widgetDeleter)> upw3(new Widget, widgetDeleter); std::shared_ptr<Widget> spw3(new Widget, widgetDeleter); //2.2 make系列函數不能接受{}初始化 auto upv = std::make_unique<std::vector<int>>(10, 20); //10個元素,每一個都是20。而不是隻有兩個元素 auto spv = std::make_shared<std::vector<int>>(10, 20); //同上 auto pw1 = new Widget(10, 20); //使用圓括號,匹配Widget(int x, int y) auto pw2 = new Widget{ 10, 20 }; //使用大括號,匹配Widget(initializer_list) delete pw1; delete pw2; auto spw = std::make_shared<Widget>(10, 20); //使用圓括號,匹配Widget中非initializer_list形參的構造函數 //auto spw = std::make_shared<Widget>({10,20}); //error,make沒法轉發大括號初始化列表(緣由見《完美轉發》一課) auto initList = { 10, 20 }; //initList推導爲initializer_list<int> auto splst = std::make_shared<Widget>(initList); //ok,匹配Widget(const std::initializer_list<int> li) //2.3 對象的內存可能沒法及時回收 auto pBigObj = std::make_shared<ReallyBigType>(); //經過make_shared建立大對象 //... //建立指向大對象的多個std::shared_ptr和std::weak_ptr,並使用這些智能指針來操做對象 //... //最後一個指向大對象的shard_ptr在此析構,但若干weak_ptr仍然存在 //... //此時,內存塊只析構,還沒回收。由於weak_ptr還共享着內存塊中的控制塊 //... //最後一個指向大對象的weak_ptr析構,內存塊(託管對象+控制塊)才被回收。因爲weak_ptr的生命期比shared_ptr長, //出現了內存塊延遲迴收的現象。 //使用new方法則不會出現上述現象 shared_ptr<ReallyBigType> pBigObj2(new ReallyBigType); //經過new,而不是make_shared建立 //... //同前,建立指向多個指向大對象的shared_ptr和weak_ptr。 //... //最後一個指向大對象的shard_ptr在此析構,但若干weak_ptr仍然存在。此時大對象的內存因爲強引用爲0,被回收。 //... //此階段,僅控制塊佔用的內存處於未回收狀態。 //... //最後一個指向該對象的weak_ptr析構,控制塊被回收。 return 0; }