不少編程語言都有GC的機制,能夠自動管理內存資源,而後GC機制帶來的是資源釋放的不肯定性,c++原始的手工管理內存資源的方式雖然具備釋放的肯定性,可是人工管理很是容易出錯;如何既能自動釋放內存又能保證肯定性呢,modern c++給出的方案是shared_ptr。c++
從c++11開始引入的shared_ptr,用來表示指針對指向對象的「共享全部權」;一個對象能夠被多個shared_ptr指向和訪問,這些shared_ptr類型的指針共同享有該對象的全部權,當最後一個指向該對象的shared_ptr生命週期結束的時候,對象被銷燬。編程
shared_ptr基於引用計數實現,shared_ptr的構造將引用計數加1,銷燬的時候引用計數減1,而賦值則將源指針引用計數加1,目標指針引用計數減1,例如P1=P2,P1指向對象的引用計數減1,P2指向對象的引用計數加1。當引用計數減1以後爲0的時候,shared_ptr將會銷燬指向的對象。數組
存儲引用計數的空間是動態分配的。此外,爲了線程安全,引用計數的加減都必須是原子操做,原子操做的實現帶來了性能上的損耗;上面提到,shared_ptr的構造函數函數會增長引用計數,可是移動構造除外,由於移動構造並無增長指向對象的引用計數,因此不須要改變引用計數;安全
與unique_ptr相似,shared_ptr一樣也支持自定義銷燬方法(默認是直接調用delete),與unique_ptr不一樣的是,銷燬方式是unique_ptr類型的一部分,而shared_ptr的銷燬方式卻不是。編程語言
auto loggingDel = [](Widget *pw) { makeLogEntry(pw); delete pw; }; std::unique_ptr<Widget, decltype(loggingDel)> upw(new Widget, loggingDel); std::shared_ptr<Widget> spw(new Widget, loggingDel);
不把銷燬方式做爲shared_ptr類型的一部分能夠帶來更大的靈活性,由於這裏不一樣的shared_ptr<Widget>指針對象可能須要不一樣的銷燬方式函數
auto customDeleter1 = [](Widget *pw) { … }; // custom deleters, auto customDeleter2 = [](Widget *pw) { … }; // each with adifferent type std::shared_ptr<Widget> pw1(new Widget, customDeleter1); std::shared_ptr<Widget> pw2(new Widget, customDeleter2);
另外一個與unique_ptr不一樣的是,自定義銷燬方式並不會改變shared_ptr的size,shared_ptr的size始終是兩倍的裸指針size,其內存佈局是以下圖所示:佈局
由圖中能夠看到,實際上引用計數、自定義銷燬等都不是直接存儲在shared_ptr中,而是經過一個指針指向的一個控制塊存儲的,控制塊是動態分配的內存,對shared_ptr進行不一樣的操做時,須要判斷須要不要分配新的控制塊,控制塊的分配主要有如下幾種狀況:性能
使用std::make_shared的時候老是分配控制塊this
shared_ptr由unique_ptr或裸指針構建時分配控制塊spa
shared_ptr由其餘shared_ptr或weak_ptr構建時不分配新的控制塊,而是沿用既有智能指針的控制塊
由上面的2引出的一個問題,當咱們用一個裸指針構建多個shared_ptr時,會分配多個控制塊,這就致使一個問題,同一個對象確有多個引用計數(控制塊),這就很容易致使一個對象被銷燬屢次,下面的代碼描述了這種狀況:
auto pw = new Widget; // pw is raw ptr //… std::shared_ptr<Widget> spw1(pw, loggingDel); // create control block for *pw std::shared_ptr<Widget> spw2(pw, loggingDel); // create 2nd control block
有兩個方法能夠避免上面的問題發生,
1 儘量避免使用裸指針來構建shared_ptr,使用make_shared
2 必須使用裸指針的話,new出對象後直接傳入,而不是將指針傳入
std::shared_ptr<Widget> spw1(new Widget, loggingDel);
另一種狀況是涉及到this指針的問題,假如在某個類的實現代碼中,用this指針構建了一個shared_ptr的時候:
class Widget { public: … void process(){ … processedWidgets.emplace_back(this); //add it to the list of processed widgets } … private: std::vector<std::shared_ptr<Widget>> processedWidgets; };
上面這段代碼是能夠正常編譯的,這裏使用this指針構建了一個shared_ptr並保存到了一個vector中,這就給當前對象生成了一個控制塊,可是實際上,因爲外部使用shared_ptr管理這個對象,該對象已經存在一個控制塊了;
std::shared_ptr<Widget> spw1(new Widget);//產生一個控制塊
爲了解決這個問題,標準庫引入了一個新的模板類enable_shared_from_this,自定義的類能夠經過繼承這個模板類來規避由this指針構建shared_ptr的問題,上面的代碼修改以後以下面所示:
class Widget: public std::enable_shared_from_this<Widget> { public: … void process() { // as before, process the Widget … // add std::shared_ptr to current object to processedWidgets processedWidgets.emplace_back(shared_from_this()); } … };
經過調用enable_shared_from_this類的shared_from_this函數來獲取對象自己的shared_ptr指針,就不會再建立一個新的控制塊了。shared_from_this會自動去查找關聯了當前對象的控制塊,並建立一個shared_ptr指針引用已有的控制塊,實際的狀況中,對象函數被調用必然是在對象已經存在的前提下,因此當前對象關聯的控制塊老是存在的,若是shared_from_this未查找到當前對象關聯的控制塊,就會致使未定義行爲,一般會拋出一個異常。
爲了不上面的狀況,能夠把類的構造函數設爲私有,再經過一個工廠方法函數返回shared_ptr來確保客戶端在調用的時候對象必定關聯了控制塊
class Widget: public std::enable_shared_from_this<Widget> { public: // factory function that perfect-forwards args // to a private ctor template<typename... Ts> static std::shared_ptr<Widget> create(Ts&&... params); … void process(); // as before … private: … // ctors };
關於shared_ptr性能的討論
shared_ptr的控制塊是動態生成的,儘管佔用的空間並不大,可是控制塊的實際實現比想象的要複雜,實現控制塊使用到了繼承和虛函數,同時引用計數的增減是原子操做也增長了性能上的代價,這些都致使了shared_ptr並非管理全部動態資源的最好方案,使用shared_ptr解引用獲取對象時會比直接使用裸指針的代價更高;
然而,儘管shared_ptr有在性能上付出了必定的代價,其帶來的收益是很是顯著的,shared_ptr解決了動態分配資源的生命週期自動管理,大多數時候,在「共享全部權」的語義下,使用shared_ptr管理動態資源都是值得推薦的;而沒有「共享全部權」語義的其餘狀況下,例如「獨佔全部權」,則可使用unique_ptr來代替;
另外一個shared_ptr不能作的事情是管理數組,不能吃std::shared_ptr<T[]>這樣的類型,然而,c++ 11以後標準庫已經引入了std::array,shared_ptr管理一個std::array類型的對象是可行的。
shared_ptr的注意點
shared_ptr能夠用來管理具備「共享全部權」語義的動態資源,能夠自動管理對象的生命週期和GC
因爲control_block的存在,shared_ptr的size一般是2倍裸指針或unique_ptr的大小,此外,shared_ptr的引用計數增減是原子操做
shared_ptr一樣支持自定義銷燬方式,且自定義銷燬方式與shared_ptr類型無關
避免使用裸指針構建shared_ptr,在有經過this指針構建shared_ptr的狀況下要繼承std::enable_shared_from_this