一. shared_ptr的基本用法ios
(一)與unique_ptr的比較編程
比較數組 |
shared_ptr安全 |
unique_ptr併發 |
備註ide |
初始化函數 |
①shared_ptr<T> sp;this sp.reset(new T());spa ②shared_ptr<T> sp(new T());線程 ③shared_ptr<T> sp1 = sp; //拷貝構造 ④auto sp = make_shared<int>(10); |
①unique_ptr<T> up; up.reset(new T()); ②unique_ptr<T> up(new T()); ③unique_ptr<T> up1 = std::move(up);//移動構造 ④auto up = make_unique<int>(10); |
二者的構造函數將聲明爲explicit,即不容許隱式類型轉換,如shared_ptr<int> sp = new int(10); |
條件判斷 |
如,if(sp){…} |
如,if(up){…} |
兩都均重載operator bool() |
解引用 |
*sp |
*up |
解引用,得到它所指向的對象 |
->mem |
sp->mem |
up->mem |
重載->運算符 |
get() |
sp.get() |
up.get() |
返回智能指針中保存的裸指針,要當心使用。 |
p.swap(q) |
sp.swap(q); |
up.swap(q); |
交換p和q指針 |
獨有操做 |
①shared_ptr<T> p(q);//拷貝構造 ②p = q;//賦值 ③p.unique();若p.use_count()爲1,返回true,不然返回false。 ④p.use_count()//返回強引用計數 |
①up=nullptr;釋放up指向的對象,並將up置空。 ②up.release();//up放棄對指針的控制權,返回裸指針,並將up置空 ③up.reset();釋放up指向的對象。 up.reset(q);其中q爲裸指針。令up指向q所指對象。 up.reset(nullptr);置空 |
注意: ①unique_ptr不可拷貝和賦值,但能夠被移動。 ②release會切斷unique_ptr和它原來管理的對象間的聯繫。一般用來初始化另外一個智能指針。 |
(二)指定刪除器
1. shared_ptr<T> sp1(q, deleter1);與unique_ptr不一樣,刪除器不是shared_ptr類型的組成部分。假設,shared_ptr<T> sp2(q,deleter2),儘管sp1和sp2有着不一樣的刪除器,但二者的類型是一致的,均可以被放入vector<shared_ptr<T>>類型的同一容器裏。
2. 與std::unique_ptr不一樣,自定義刪除器不會改變std::shared_ptr的大小。其始終是祼指針大小的兩倍。
3. 當使用shared_ptr管理動態數組時,須要指定刪除器。由於默認刪除器不支持數組對象。如shared_ptr<int> sp(new int[10], std::default_delete<int[]>);
4. 刪除器能夠是普通函數、函數對象和lambda表達式等。默認的刪除器爲std::default_delete,其內部是經過delete來實現功能的。
二. 剖析std::shared_ptr
(一)std::shared_ptr的內存模型
1. shared_ptr包含了一個指向對象的指針和一個指向控制塊的指針。每個由std::shared_ptr管理的對象都有一個控制塊,它除了包含引用計數以外,還包含了自定義刪除器的副本和分配器的副本以及其餘附加數據。
2. 控制塊的建立規則:
(1)std::make_shared老是建立一個控制塊。
(2)從具有全部權的指針出發構造一個std::shared_ptr時,會建立一個控制塊。(如std::unique_ptr轉爲shared_ptr時會建立控制塊,由於unique_ptr自己不使用控制塊,同時unique_ptr置空)
(3)當std::shared_ptr構造函數使用裸指針做爲實參時,會建立一個控制塊。這意味從同一個裸指針出發來構造不止一個std::shared_ptr時會建立多重的控制塊,也意味着對象會被析構屢次。若是想從一個己經擁有控制塊的對象出發建立一個std::shared_ptr,能夠傳遞一個shared_ptr或weak_ptr而非裸指針做爲構造函數的實參,這樣則不會建立新的控制塊。
【經驗】
①儘量避免將裸指針傳遞給一個std::shared_ptr的構造函數,經常使用的替代手法是使用std::make_shared。
②若是必須將一個裸指針傳遞給shared_ptr的構造函數,就直接傳遞new運算符的結果,而非傳遞一個裸指針變量。如shared_ptr<Widget> spw (new Widget, logginDel);
③不要將this指針返回給shared_ptr。當但願將this指針託管給shared_ptr時,類須要繼承自std::enable_shared_from_this,而且從shared_from_this()中得到shared_ptr指針。(具體見《enable_shared_from_this》部分的分析)
3. 引用計數(強引用計數)
(1)shared_ptr的構造函數會使該引用計數遞增,而析構函數會使該計數遞減。但移動構造時表示從一個己有的shared_ptr移動構造到一個新的shared_ptr。這意味着一旦新的shared_ptr產生後,原有的shared_ptr會被置空,其結果是引用計數沒有變化。
(2)複製賦值同時執行兩種操做(如sp1 和sp2是指向不一樣對象的shared_ptr,則sp1 = sp2時,將修改sp1使得其指向sp2所指的對象。而最初sp1所指向的對象的引用計數遞減,同時sp2所指向的對象引用計數遞增)
(3)reset函數,若是不帶參數時,則引用計數減1。若是不帶參數時,如sp.reset(p)則sp原來指向的對象引用計數減1,同時sp指向新的對象(p)
(4)若是實施一次遞減後最後的引用計數變成0,即再也不有shared_ptr指向該對象,則會被shared_ptr析構掉。
(5)引用計數的遞增和遞減是原子操做,即容許不一樣線程併發改變引用計數。
【編程實驗】shared_ptr的陷阱分析
#include <iostream> #include <vector> #include <memory> // for smart pointer using namespace std; class Widget{}; void func(shared_ptr<Widget> sp){} int funcException() { /*throw 1;*/ return 0; } //假設該函數會拋出異常 void demo(shared_ptr<int> sp, int f){} int main() { //1. 陷阱:用同一裸指針建立多個shared_ptr //1.1 錯誤作法 auto pw = new Widget; std::shared_ptr<Widget> spw1(pw); //強引用計數爲1,爲pw建立一個控制塊 //std::shared_ptr<Widget> spw2(pw); //強引用計數爲1,爲pw建立另外一個新的控制塊,會致使屢次析構 auto sp = new Widget; func(shared_ptr<Widget>(sp)); //慎用裸指針,sp將在func結束後被釋放! //1.2 正確作法 std::shared_ptr<Widget> spw3(spw1); //ok,pw的強引用計數爲2。使用與spw1同一個控制塊。 std::shared_ptr<Widget> spw4(new Widget); //將new的結果直接傳遞給shared_ptr std::shared_ptr<Widget> spw5 = std::make_shared<Widget>(); //強烈推薦的作法! //2. 陷阱:在函數實參中建立shared_ptr //2.1 shared_ptr與異常安全問題 //因爲參數的計算順序因編譯器和調用約定而異。假定按以下順序計算 //A.先前new int,而後funcException(); //B.假設剛好此時funcException產生異常。 //C.因異常出現shared_ptr還來不及建立,因而int內存泄露 demo(shared_ptr<int>(new int(100)), funcException()); //2.2 正確作法 auto p1 = std::make_shared<int>(100); demo(p1, funcException()); //3. 陷阱:shared_ptr的循環引用(應避免)(見第22課 weak_ptr) //4. 刪除器 auto deleter1 = [](Widget* pw) {cout << "deleter1"<< endl; delete pw; }; auto deleter2 = [](Widget* pw) {cout << "deleter2"<< endl; delete pw; }; std::shared_ptr<Widget> pw1(new Widget, deleter1); std::shared_ptr<Widget> pw2(new Widget, deleter2); std::shared_ptr<Widget> pw3(pw1); pw3.reset(new Widget); //deleter恢復爲默認的std::default_delete vector<std::shared_ptr<Widget>> vecs; vecs.emplace_back(pw1); vecs.emplace_back(pw2); //pw1和pw2雖然有不一樣的刪除器,但類型相同,能夠放入同一容器內。 //5. 其它 //5.1 shared_ptr的大小 cout << sizeof(spw1) << endl;//8 cout << sizeof(pw1) << endl; //8 //5.2 shared_ptr管理動態數組(建議用std::array、std::vector取代) std::shared_ptr<int> pArray1(new int[10], [](int* p) {delete[] p; }); //使用delete[] std::shared_ptr<int> pArray2(new int[10], std::default_delete<int[]>()); //使用default_delete<int[]>() //5.3 常見操做 cout << pw1.use_count() << endl; //2 if (pw1) //pw1.use_count >= 1 ? { cout << "pw1.use_count >= 1" << endl; } else { cout << "pw1.use_count == 0" << endl; } //5.4 別名構造 int* p = new int(10); std::shared_ptr<int> a(new int(20)); std::shared_ptr<int> b(a, p); // alias constructor: co-owns a, points to p。可用於多繼承中 // a 和 b擁用相同的控制塊,但二者指向的對象不一樣。因爲二者擁用相同的 //的控制塊,可認爲a和b所指對象具備相同的擁有者,所以10和20兩個堆對象 //擁有相同的生命期 cout << *a << endl; //20 cout << *b << endl; //10 return 0; }
四. enable_shared_from_this模板的分析
(一)模板分析(以boost::enable_shared_from_this爲例)
template<class T> class enable_shared_from_this { protected: enable_shared_from_this() BOOST_NOEXCEPT { } enable_shared_from_this(enable_shared_from_this const &) BOOST_NOEXCEPT { } enable_shared_from_this & operator=(enable_shared_from_this const &) BOOST_NOEXCEPT { return *this; } ~enable_shared_from_this() BOOST_NOEXCEPT // ~weak_ptr<T> newer throws, so this call also must not throw { } public: shared_ptr<T> shared_from_this() { shared_ptr<T> p( weak_this_ ); BOOST_ASSERT( p.get() == this ); return p; } shared_ptr<T const> shared_from_this() const { shared_ptr<T const> p( weak_this_ ); BOOST_ASSERT( p.get() == this ); return p; } public: // actually private, but avoids compiler template friendship issues // Note: invoked automatically by shared_ptr; do not call template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const { if( weak_this_.expired() ) { weak_this_ = shared_ptr<T>( *ppx, py ); } } private: mutable weak_ptr<T> weak_this_; };
1. enable_shared_from_this模板類提供兩個public屬性的shared_from_this成員函數。這兩個函數內部會經過weak_this_(weak_ptr類型)成員來建立shared_ptr。
2. _internal_accept_owner函數不能手動調用,這個函數會被shared_ptr自動調用,該函數是用來初始化惟一的成員變量weak_this_。
3. 根據對象生成順序,先初始化基類enable_shared_from_this,再初始化派生類對象自己。這時對象己經生成,但weak_this_成員還未被初始化,最後應經過shared_ptr<T> sp(new T())等方式調用shared_ptr構造函數(內部會調用_internal_accept_owner)來初始化weak_this_成員。而若是在調用shared_from_this函數以前weak_this_成員未被初始化,則會經過ASSERT報錯提示。
(二)使用說明
1. 基類必須爲enable_shared_from_this<T>,其中T爲派生類的類名。(這種方法叫奇妙遞歸模板模式)
2. 經過調用shared_from_this()成員函數得到一個和this指針指向相同對象的shared_ptr。
3. 從內部實現看,shared_from_this會查詢當前對象的控制塊,並建立一個指向該控制塊的新shared_ptr。這樣的設計就要求當前對象己有一個與其關聯的控制塊。爲了實現這一點,就必須有一個己經存在指向當前對象的std::shared_ptr,若是不存在,則一般shared_from_this會拋出異常。
【編程實驗】安全地從this指針建立shared_ptr
#include <iostream> #include <vector> #include <memory> using namespace std; //1. 從this指針建立shared_ptr //1.1 錯誤的作法 class Test1 { public: //析構函數 ~Test1() { cout <<"~Test1()" << endl; } //獲取指向當前對象的指針 std::shared_ptr<Test1> getObject() { shared_ptr<Test1> pTest(this); //危險! 直接從this指針建立,會爲this對象建立新的控制塊! //從而可能致使this所指對象被屢次析構 return pTest; } }; //1.2 正確的作法 class Test2 : public std::enable_shared_from_this<Test2> //繼承! 注意Test2爲基類的模板參數 (遞歸模板模式) { public: //析構函數 ~Test2() { cout << "~Test2()" << endl; } std::shared_ptr<Test2> getObject() { return shared_from_this(); //調用enable_shared_from_this模板的成員函數,獲取this對象的shared_ptr } }; //2. shared_from_this函數的正確調用 //2.1 通常作法 class Test3 : public std::enable_shared_from_this<Test3> { public: //構造函數中不能使用shared_from_this Test3() { //std::shared_ptr<Test3> sp = shared_from_this(); //error,此時基類(enable_shared_from_this<Test3>) //雖己構造完,但shared_ptr的構造函數還沒被調用,weak_this_指針 //未被初始化,所以調用shared_from_this會拋出異常 } //調用process以前,必須確保shared_ptr的構造函數己被執行(即weak_this_被初始化) void process() { std::shared_ptr<Test3> sp = shared_from_this(); } }; //2.2 改進作法:利用工廠函數來提供shared_ptr class Test4 : public std::enable_shared_from_this<Test4> { Test4() {} //構造函數設爲private public: //提供工廠函數 template<typename... Ts> static std::shared_ptr<Test4> create(Ts&& ... params) { std::shared_ptr<Test4> ret(new Test4(params...)); return ret; } void process() { std::shared_ptr<Test4> sp = shared_from_this(); } }; //3. enable_shared_from_this的應用舉例 class Widget; std::vector<std::shared_ptr<Widget>> processWidgets; //記錄己被處理過的Widgets class Widget : public std::enable_shared_from_this<Widget> //須要從這裏繼承 { public: void process() { //錯誤作法:直接將this傳給shared_ptr<Widget> //processWidgets.emplace_back(this); //將處理完的Widget加入鏈表。 //error,這種作法本質上是用裸指針來建立shared_ptr,會爲this對象建立 //新的控制塊。若是外部new Widget時,也將指針交給shared_ptr管理時,會出現爲同 //一個this對象建立多個控制塊,從而形成this對象的屢次析構! //正確作法:(爲了確保shared_from_this在shared_ptr構造函數後被調用,能夠採用工廠函數的方式來建立Widget, //具體見前面的例子) processWidgets.emplace_back(shared_from_this()); //將指向當前對象的shared_ptr加入到鏈表中 } ~Widget() { cout <<"~Widget()" << endl; } }; int main() { //1. 從this指針建立shared_ptr //1.1 錯誤作法:對象被屢次析構 { //std::shared_ptr<Test1> pt1(new Test1()); //std::shared_ptr<Test1> pt2 = pt1->getObject(); } //1.2 正確作法 { std::shared_ptr<Test2> pt1(new Test2()); std::shared_ptr<Test2> pt2 = pt1->getObject(); } //2. shared_from_this的正確調用 { //2.1 錯誤方法: Test3 t; //t.process(); //錯誤,shared_ptr構造函數沒有被執行 Test3* pt = new Test3(); //pt->process(); //錯誤,緣由同上。 delete pt; //正確作法 std::shared_ptr<Test3> spt(new Test3); //shared_ptr構造被執行,weak_this_被正確初始化 spt->process(); //2.2 工廠方法提供shared_ptr,確保shared_ptr構造函數被執行! std::shared_ptr<Test4> spt2 = Test4::create(); spt2->process(); } //3. enable_shared_from_this的應用舉例 { std::shared_ptr<Widget> sp(new Widget); sp->process(); } return 0; }