C#
、Java
、python
和go
等語言中都有垃圾自動回收機制,在對象失去引用的時候自動回收,並且基本上沒有指針的概念,而C++
語言不同,C++
充分信任程序員,讓程序員本身去分配和管理堆內存,若是管理的很差,就會很容易的發生內存泄漏問題,而C++11
增長了智能指針(Smart Pointer)。主要分爲shared_ptr
、unique_ptr
和weak_ptr
三種,使用時須要引用頭文件<memory>
。c++98
中還有auto_ptr
,基本被淘汰了,不推薦使用。而c++11
中shared_ptr
和weak_ptr
都是參考的boost
庫中實現的。java
個人簡書連接python
c
語言中最常使用的是malloc()
函數分配內存,free()
函數釋放內存,而c++
中對應的是new
、delete
關鍵字。malloc()
只是分配了內存,而new
則更進一步,不只分配了內存,還調用了構造函數進行初始化。使用示例:ios
int main() { // malloc返回值是 void* int* argC = (int*)malloc(sizeof(int)); free(argC); char*c = (char*)malloc(100); free(c); char *age = new int(25); //作了兩件事情 1.分配內存 2.初始化 int* height = new int(160); delete height; delete age; char* arr = new int[100]; delete[] arr; /*delete數組須要使用delete[],事實上,c++原始支持的數據結構組成的 數組不須要[]也能夠,但 自定義的數據類型組成的數組必須使用delete[]*/ }
new
和delete
必須成對出現,有時候是不當心忘記了delete
,有時候則是很難判斷在這個地方本身是否是該delete
,這個和資源的生命週期有關,這個資源是屬於我這個類管理的仍是由另一個類管理的,若是是我管理的,就由我來delete
,由別人管理的就由別人delete
,我就算析構了也不影響該資源的生命週期。例如:c++
// 狀況1: 須要本身delete const char* getName() { char *valueGroup = new char[1000]; // do something return valueGroup; } // 狀況2: 不須要本身delete const char* getName2() { static char valueGroup[1000]; // do something return valueGroup; }
只經過函數簽名來看,這兩個函數沒有什麼區別,可是因爲實現的不一樣,有時候須要本身管理內存,有時候不須要,這個時候就須要看文檔說明了。這就是使用一個"裸"指針很差的地方。程序員
一點改進是,若是須要本身管理內存的話,最好顯示的將本身的資源傳遞進去,這樣的話,就能知道是該資源確實應該由本身來管理。編程
char *getName(char* v, size_t bufferSize) { //do something return v; }
上面仍是小問題,本身當心一點,再仔細看看文檔,仍是有機會避免這些狀況的。可是在c++
引入異常的概念以後,程序的控制流就發生了根本性的改變,在寫了delete的時候仍是有可能發生內存泄漏。以下例:數組
void badThing(){ throw 1;// 拋出一個異常 } void test() { char* a = new char[1000]; badThing(); // do something delete[] a; } int main() { try { test(); } catch (int i){ cout << "error happened " << i << endl; } }
上面的new
和delete
是成對出現的,可是程序在中間的時候拋出了異常,因爲沒有當即捕獲,程序從這裏退出了,並無執行到delete
,內存泄漏仍是發生了。安全
C++
中的構造函數和析構函數十分強大,可使用構造和析構解決這種問題,好比:服務器
class SafeIntPointer { public: explicit SafeIntPointer(int v) : m_value(new int(v)) { } ~SafeIntPointer() { delete m_value; cout << "~SafeIntPointer" << endl; } int get() { return *m_value; } private: int* m_value; }; void badThing(){ throw 1;// 拋出一個異常 } void test() { SafeIntPointer a(5); badThing(); } int main() { try { test(); } catch (int i){ cout << "error happened " << i << endl; } } // 結果 // ~SafeIntPointer // error happened 1
能夠看到,就算髮生了異常,也可以保證析構函數成功執行!這裏的例子是這個資源只有一我的使用,我不用了就將它釋放掉。但還有種狀況,一份資源被不少人共同使用,要等到全部人都再也不使用的時候才能釋放掉,對於這種問題,就須要對上面的SafeIntPointer
增長一個引用計數,以下:數據結構
class SafeIntPointer { public: explicit SafeIntPointer(int v) : m_value(new int(v)), m_used(new int(1)) { } ~SafeIntPointer() { cout << "~SafeIntPointer" << endl; (*m_used) --; //引用計數減1 if(*m_used <= 0){ delete m_used; delete m_value; cout << "real delete resources" << endl; } } SafeIntPointer(const SafeIntPointer& other) { m_used = other.m_used; m_value = other.m_value; (*m_used)++; //引用計數加1 } SafeIntPointer& operator= (const SafeIntPointer& other) { if (this == &other) // 避免自我賦值!! return *this; m_used = other.m_used; m_value = other.m_value; (*m_used)++; //引用計數加1 return *this; } int get() { return *m_value; } int getRefCount() { return *m_used; } private: int* m_used; int* m_value; }; int main() { SafeIntPointer a(5); cout << "ref count = " << a.getRefCount() << endl; SafeIntPointer b = a; cout << "ref count = " << a.getRefCount() << endl; SafeIntPointer c = b; cout << "ref count = " << a.getRefCount() << endl; } /* ref count = 1 ref count = 2 ref count = 3 ~SafeIntPointer ~SafeIntPointer ~SafeIntPointer real delete resources */
能夠看到每一次賦值,引用計數都加一,最後每次析構一次後引用計數減一,知道引用計數爲0,才真正釋放資源。要寫出一個正確的管理資源的包裝類仍是蠻難的,好比上面那個上面例子就不是線程安全的,只能屬於一個玩具,在實際工程中簡直無法用。而到了C++11
,終於提供了一個共享的智能指針解決這個問題。
shared_ptr
的基本使用很簡單,看幾個例子就明白了:
#include <iostream> #include <memory> class Object { public: Object(int id) : m_id(id) { std::cout << "init obj " << std::endl; } ~Object() { std::cout << "bye bye" << m_id << std::endl; } int id() const { return m_id; } private: int m_id; }; // 取個別名 讓寫起來更方便 typedef std::shared_ptr<Object> ObjectPtr; void print(Object* obj) { std::cout << "in print(Object* obj) : " << std::endl; // std::cout << "ref count is " << obj.use_count() << std::endl; } void print(Object obj) { std::cout << "in print(Object obj) : " << std::endl; // std::cout << "ref count is " << obj.use_count() << std::endl; } void print(ObjectPtr obj) { std::cout << "in print(ObjectPtr obj) : "; std::cout << "ref count is " << obj.use_count() << std::endl; } void printRef(const ObjectPtr& obj) { std::cout << "in print(const ObjectPtr& obj) : "; std::cout << "ref count is " << obj.use_count() << std::endl; } int main() { // 建立一個智能指針,管理的資源就是 new出來的Object(1) //ObjectPtr obj=new Object(1); //不能將一個原始指針直接賦值給一個智能指針 ObjectPtr obj(new Object(1)); // 正確 std::cout << "ref count is " << obj.use_count() << std::endl; // 1 ObjectPtr obj2(obj); std::cout << "ref count is " << obj.use_count() << std::endl; // 2 std::cout << "ref count is " << obj2.use_count() << std::endl; // 2 obj和obj2管理的資源是同樣的 ObjectPtr obj3 = obj2; std::cout << "ref count is " << obj.use_count() << std::endl; // 3 obj2.reset(); // obj2再也不管理以前的資源,資源的引用計數會減1 // 或者能夠寫成 obj2 = nullptr; std::cout << "ref count is " << obj.use_count() << std::endl; // 2 ObjectPtr obj4; //obj4 開始沒有管理資源 // 將管理的資源 相互交換 // 交換後 obj3沒有再管理資源, obj4管理obj3以前管理的資源 // 或者寫成 std::swap(obj3, obj4); obj3.swap(obj4); std::cout << "ref count is " << obj.use_count() << std::endl; // 仍是 2 // 還能夠從智能指針中獲取原始指針 auto p = obj.get(); // auto = Object* // 須要判斷這個obj是否確實管理着資源, 可能爲 nullptr if( p ) { std::cout << "p->id() is " << p->id() << std::endl; std::cout << "(*p).id() is " << (*p).id() << std::endl; } // 智能指針也能夠像普通指針同樣使用 // 重載了 operator bool if( obj ) { std::cout << "obj->id() is " << obj->id() << std::endl; // 重載了operator -> std::cout << "(*obj).id() is " << (*obj).id() << std::endl;// 重載了operator * } // obj.use_count()能夠判斷當前有多少智能指針在管理資源 // 若是判斷是否是隻有一我的在管理這個資源, 用unique()函數更加高效 // unique() 等價於 obj.use_count() == 1 obj4 = nullptr; // obj4不在管理,這個時候的引用計數變成了 1 std::cout << "ref count is " << obj.use_count() << std::endl; // 1 if( obj.unique() ) std::cout << "only one hold ptr "<< std::endl; else std::cout << "not noly one hold ptr" << std::endl; //其實也有可能沒有人再管理 // 將智能指針看成參數傳遞給函數時 // 若是是值傳遞, 智能指針發生一次拷貝, // 在函數內部時智能指針的引用計數會 + 1 // 離開函數做用域時, 智能指針會析構, 引用計數會 - 1 print(obj); // 若是傳遞的是引用, 對引用計數沒影響 並且工做量比較小(沒有拷貝) // 推薦使用引用方式傳遞, 傳值的方式也有用處,好比多線程時 printRef(obj); // 還能夠不傳遞智能指針, 傳遞原生類型 print(*obj); //傳Object類型的時候,離開函數的時候參數obj會發生一次析構 print(obj.get()); } /* init obj ref count is 1 ref count is 2 ref count is 2 ref count is 3 ref count is 2 ref count is 2 p->id() is 1 (*p).id() is 1 obj->id() is 1 (*obj).id() is 1 ref count is 1 only one hold ptr in print(ObjectPtr obj) : ref count is 2 in print(const ObjectPtr& obj) : ref count is 1 in print(Object obj) : bye bye1 //這個是在調用print(Object obj)時,局部變量析構時打印的 in print(Object* obj) : bye bye1 //這個是在資源在沒有人引用的時候,執行析構函數產生的 */
大部分用法都基本上在上面的例子中體現出來了,當沒有人引用這個資源的時候,智能指針的默認行爲是調用 delete銷燬這個資源,而咱們也能夠人爲指定這個步驟,由於有些資源不必定是new
出來的,因此不該該使用默認的delete
行爲,還有一個狀況是,在用智能指針管理動態數組的時候,須要本身指定刪除器函數。
#include <iostream> #include <memory> class Object { public: Object(int id) : m_id(id) { std::cout << "init obj " << std::endl; } ~Object() { std::cout << "bye bye" << m_id << std::endl; } int id() const { return m_id; } private: int m_id; }; // 讓寫起來更方便 typedef std::shared_ptr<Object> ObjectPtr; void deleterOfObject(Object* obj) { if ( obj ) { std:: cout << "delete obj " << obj->id() << std::endl; delete obj; } } void useDeleter() { //指定刪除動做 使用外面定義的函數 ObjectPtr obj(new Object(2), deleterOfObject); ObjectPtr obj2 = obj; // obj 和 obj2 會在離開這個函數的時候析構,因而,就調用了 deleterOfObject //管理數組 使用匿名函數看成刪除函數 std::shared_ptr<int> p(new int[10], [](int* p){ std::cout << "delete[] p" << std::endl; delete[] p; //須要使用delete[] }); // vector<> 不必使用智能指針, 不用new 和 delete... 內部已經管理了 } int main() { useDeleter(); } /* init obj delete[] p //注意析構和構造的順序是相反的 delete obj 2 bye bye2 */
shared_ptr
主要就是利用變量出了做用域以後析構函數必定能被調用到,哪怕是出現了異常。
例以下面的例子:
#include <iostream> #include <memory> class Object { public: Object(int id) : m_id(id) { std::cout << "init obj " << std::endl; } ~Object() { std::cout << "bye bye " << m_id << std::endl; } int id() const { return m_id; } private: int m_id; }; // 讓寫起來更方便 typedef std::shared_ptr<Object> ObjectPtr; int main() { Object *obj = new Object(2); ObjectPtr p1(obj); ObjectPtr p2(obj); std::cout << p1.use_count() << " " << p2.use_count() << std::endl; std::cout << "finished" << std::endl; } /* init obj 1 1 finished bye bye 2 bye bye 203200 //m_id成爲了隨機數 */
能夠發現,雖然是用的同一個指針初始化了兩個shared_ptr
,可是這兩個shared_ptr
卻沒有關聯,它們的引用計數都是1
,而後問題就發生了,p2
先析構,因而引用計數變爲了0
,就開始刪除它管理的資源obj
,因而obj
就被析構了,這是還算正常,接着析構p1
,引用計算也變成了0
,它也開始刪除本身管理的資源obj
,至關於屢次delete
了同一個對象,m_id
成爲了隨機數,這還算好的狀況,若是Object
內部還有指針,或者obj
的地址被其餘變量佔據了,delete
掉這塊內存就會發生嚴重的錯誤!並且很差發現緣由。
其實就是因爲上面的緣由,咱們不可能傳遞this
指針給shared_ptr
,由於用同一個指針初始化兩個shared_ptr
,它們之間並無關聯,以下面的例子:
#include <iostream> #include <memory> class Y { public: std::shared_ptr<Y> f(){ return std::shared_ptr<Y>(this); } }; int main() { std::shared_ptr<Y> p1(new Y()); std::shared_ptr<Y> p2 = p1->f(); // p2是由this構造共享智能指針 std::cout << p1.use_count() << " " << p2.use_count() << std::endl; // 1 1 }
從上面的例子能夠看出,返回由this
構造的shared_ptr
並無用,返回還可能形成嚴重錯誤(因爲可能屢次delete
)!解決辦法是繼承std::enable_shared_from_this<Y>
,而後使用shared_from_this()
構造shared_ptr
。
#include <iostream> #include <memory> class Y : public std::enable_shared_from_this<Y> { public: std::shared_ptr<Y> f(){ return shared_from_this(); } }; int main() { std::shared_ptr<Y> p1(new Y()); std::shared_ptr<Y> p2 = p1->f(); // p2是由p1的thiss構造共享智能指針 std::cout << p1.use_count() << " " << p2.use_count() << std::endl; // 2 2 std::shared_ptr<Y> p3(new Y()); std::shared_ptr<Y> p4 = p3->f(); // p4是由p3的this構造的構造共享智能指針 std::cout << p1.use_count() << " " << p2.use_count() << " " << p3.use_count() << " " << p4.use_count() << std::endl; // 2 2 2 2 }
能夠發現引用計數確實增長了。而且由p1
獲得的shared_from_this()
增長的就是p1
的引用計數,p3
獲得的shared_from_this()
增長的就是p3
的引用計數,這和this
的含義是同樣的。因此咱們在類內部須要傳遞this
指針給shared_ptr
時,須要繼承自std::enable_shared_from_this<T>
,而且使用shared_from_this()
替代this
。而shared_from_this()
就是藉助了weak_ptr
。原理在後面再講。
其實上面使用的智能指針構造方式有一點點問題,ObjectPtr obj(new Object(1));
這一個語句其實調用了兩次new
,一次是new Object(1)
,另外一次是構造內部的引用計數變量的時候,那有沒有辦法只掉用一次new
呢,答案就是使用make_shared<T>()
模板函數,它將資源和引用計數變量一塊兒new
出來,例如:
#include <iostream> #include <memory> #include <cassert> class Object { public: Object(int id) : m_id(id) { std::cout << "init obj " << std::endl; } ~Object() { std::cout << "bye bye " << m_id << std::endl; } int id() const { return m_id; } private: int m_id; }; // 讓寫起來更方便 typedef std::shared_ptr<Object> ObjectPtr; int main() { // 和 ObjectPtr obj(new Object(2)); 同樣 // 可是隻調用了一次new ObjectPtr obj = std::make_shared<Object>(2); }
然而,這個函數也有失效的時候,若是管理的資源對象的構造函數是私有的他就沒有辦法了。
在有些狀況下,shared_ptr
也會碰見很尷尬、不能處理的狀況,那就是循環引用,看下面的例子:
#include <iostream> #include <memory> class Parent; //Parent類的前置聲明 typedef std::shared_ptr<Parent> ParentPtr; class Child { public: ParentPtr father; ~Child() { std::cout << "bye child" << std::endl; } }; typedef std::shared_ptr<Child> ChildPtr; class Parent { public: ChildPtr son; ~Parent() { std::cout << "bye parent" << std::endl; } }; void testParentAndChild() { ParentPtr p(new Parent()); // 1 資源A ChildPtr c(new Child()); // 2 資源B p->son = c; // 3 c.use_count() == 2 and p.use_count() == 1 c->father = p; // 4 c.use_count() == 2 and p.use_count() == 2 } int main() { testParentAndChild(); std::cout << "finished" << std::endl; } /* // 沒有調用Parent 和 Child 的析構函數 finished */
很驚訝的發現,用了shared_ptr
管理資源,資源最後仍是沒有釋放!內存泄漏仍是發生了。
分析:
1
的語句時,構造了一個共享智能指針p
,稱呼它管理的資源叫作資源A
(new Parent()
產生的對象)吧, 語句2
構造了一個共享智能指針c
,管理資源B
(new Child()
產生的對象),此時資源A
和B
的引用計數都是1
,由於只有1
個智能指針管理它們,執行到了語句3
的時候,是一個智能指針的賦值操做,資源B
的引用計數變爲了2
,同理,執行完語句4
,資源A
的引用計數也變成了2
。c
,資源B
的引用計數就變成了1
;接下來繼續析構共享智能指針p
,資源A
的引用計數也變成了1
。因爲資源A
和B
的引用計數都不爲1
,說明還有共享智能指針在使用着它們,因此不會調用資源的析構函數!A
的引用計數想變成0
,則必須資源B
先析構掉(從而析構掉內部管理資源A
的共享智能指針),資源B
的引用計數想變爲0
,又得依賴資源A
的析構,這樣就陷入了一個死循環。要想解決這個問題,只能引入新的智能指針weak_ptr
,顧名思義,弱引用,也就是不增長引用計數,它無論理shared_ptr
內部管理的指針,他只是起一個監視的做用。它監視的不是shared_ptr
自己,而是shared_ptr
管理的資源!!!weak_ptr
沒有重載操做符*
和->
,它不能直接操做資源,可是它能夠獲取所監視的shared_ptr
(若是資源尚未被析構的話)。
weak_ptr
使用示例:
#include <iostream> #include <memory> class Object { public: Object(int id) : m_id(id) { std::cout << "init obj " << std::endl; } ~Object() { std::cout << "bye bye" << m_id << std::endl; } int id() const { return m_id; } private: int m_id; }; // 取個別名 讓寫起來更方便 typedef std::shared_ptr<Object> ObjectPtr; void sharedPtrWithWeakPtr() { ObjectPtr obj(new Object(1)); typedef std::weak_ptr<Object> WeakObjectPtr; WeakObjectPtr weakObj(obj); //使用共享指針 初始化 弱引用指針 //weakObj 僅僅是一個監聽者,不會增長引用計數 std::cout << "obj use count is " << obj.use_count() << std::endl; // 1 { // lock() 方法返回一個 它對應的共享指針 // 下面這句話的結果是 2, 而不是1, // 說明weakObj.lock() 內部也獲得了一個新的共享指針,因此引用計數+1 // 在執行完這句話後就析構掉了,引用計數-1 std::cout << "weakObj.lock().use_count() is " << weakObj.lock().use_count() << std::endl; // 2 // 因爲發生了一次 賦值 ,因此 引用次次數 +1 // auto === ObjectPtr auto p = weakObj.lock(); //若是weakObj監視的資源存在, p就存在 std::cout << "obj use count is " << obj.use_count() << std::endl; // 2 if ( p ) { // do what you want to do } else { } } // 共享指針再也不管理任何資源的時候,weakObj的行爲 // 注意:若是在obj.reset前,還存在共享指針管理它的資源 // 如 :ObjectPtr obj1(obj); weakObj.lock();仍是有效的 obj.reset(); { auto p = weakObj.lock(); if( p ) { //不該該到這裏來 std::cout << "weakObj is not null 1" << std::endl; } else { std::cout << "weakObj is null 1" << std::endl; } } // 共享指針管理其餘資源的時候,weakObj的行爲 // 注意:weak_ptr.lock() // 只有在 存在某一個shared_ptr管理的資源和該weak_ptr同樣 的時候纔有效果! obj.reset(new Object(2)); { auto p = weakObj.lock(); if( p ) { //不該該到這裏來 std::cout << "weakObj is not null 2" << std::endl; } else { std::cout << "weakObj is null 2" << std::endl; } } weakObj = obj; // 從新監視 obj // 用weakObj 判斷管理的資源是否過時 if(weakObj.expired()) { } else { } } int main() { sharedPtrWithWeakPtr(); std::cout << "finished" << std::endl; } /* init obj obj use count is 1 weakObj.lock().use_count() is 2 obj use count is 2 bye bye1 weakObj is null 1 init obj weakObj is null 2 bye bye2 finished */
由上面的例子能夠看出,weak_ptr
和初始化它的share_ptr
沒有關係,而是和share_ptr
管理的資源有關係。假如WeakObjectPtr weakObj(obj);
,若是obj.reset()
,weakObj.lock()
的返回值就是空,若是obj.reset(new Object(2));
,替換了管理對象,則一塊兒的資源就被析構了,weakObj.lock()
的返回值一樣爲空,一樣能夠推斷,若是除了obj
之外還有其餘共享智能指針一塊兒管理資源,也就是說obj.reset()
的時候資源不會被析構,weakObj.lock();
的返回值就不會爲空了。不明白的話本身寫個簡單的測試用例就知道了,如:
void sharedPtrWithWeakPtr() { ObjectPtr obj(new Object(1)); typedef std::weak_ptr<Object> WeakObjectPtr; WeakObjectPtr weakObj(obj); //使用共享指針 初始化 弱引用指針 ObjectPtr obj1 = obj; //註釋掉這句話打印的就是error, 加上這句話打印的就是ok obj.reset(); auto p = weakObj.lock(); if( p ) { std::cout << "ok" << std::endl; } else { std::cout << "error" << std::endl; } }
用weak_ptr
能夠解決上面的循環引用問題,將Child
內部的parent
指針換成weak_ptr
管理:
#include <iostream> #include <memory> class Parent; //Parent類的前置聲明 typedef std::shared_ptr<Parent> ParentPtr; typedef std::weak_ptr<Parent> WeakParentPtr; class Child { public: WeakParentPtr father; ~Child() { std::cout << "bye child" << std::endl; } }; typedef std::shared_ptr<Child> ChildPtr; //typedef std::weak_ptr<Child> WeakChildPtr; class Parent { public: //WeakChildPtr son; ChildPtr son; ~Parent() { std::cout << "bye parent" << std::endl; } }; void testParentAndChild() { ParentPtr p(new Parent()); // 1 資源A ChildPtr c(new Child()); // 2 資源B p->son = c; // 3 c.use_count() == 2 and p.use_count() == 1 c->father = p; // 4 c.use_count() == 2 and p.use_count() == 1 } int main() { testParentAndChild(); std::cout << "finished" << std::endl; } /* bye parent //成功調用析構函數 bye child finished */
修改成弱引用後,成功的釋放了資源,只要將任意一個shared_ptr
換成weak_ptr
,就能夠解決問題。固然,也能夠兩個都換成weak_ptr
,至於這三種方案誰更好,就暫時不清楚了。
std::enable_shared_from_this<T>
模板類中有一個weak_ptr
,這個weak_ptr
用來觀測this
智能指針,調用shared_from_this()
函數的時候,會在內部調用weak_ptr
的lock()
方法,將所觀測的shared_ptr
返回。這個設計要依賴於當前對象已經有了一個相應的控制塊。爲此,必須已經存在一個指向當前對象的shared_ptr
(好比在調用過shared_from_this()
成員函數以外已經有了一個)。假如沒有這樣shared_ptr
存在,那麼shared_from_this()
會拋異常。 那麼這個weak_ptr
在何時賦值的呢?答案就是在外部第一次構造shared_ptr
的時候(如以前的std::shared_ptr<Y> p1(new Y());
),對std::enable_shared_from_this<T>
進行了賦值(具體實現有點複雜,還不太懂。。),這也就爲何在調用shared_from_this()
時,必須存在一個指向當前對象的shared_ptr
的緣由了。因爲這個緣由,不要在構造函數中調用shared_from_this()
,如:
#include <iostream> #include <memory> class Y : public std::enable_shared_from_this<Y> { public: Y() { std::shared_ptr<Y> p = shared_from_this(); } std::shared_ptr<Y> f(){ return shared_from_this(); } }; int main() { std::shared_ptr<Y> p1(new Y()); } // 會拋出異常! /* terminate called after throwing an instance of 'std::bad_weak_ptr' what(): bad_weak_ptr */
unique_ptr
相對於其餘兩個智能指針更加簡單,它和shared_ptr
使用差很少,可是功能更爲單一,它是一個獨佔型的智能指針,不容許其餘的智能指針共享其內部的指針,更像原生的指針(但更爲安全,可以本身釋放內存)。不容許賦值和拷貝操做,只可以移動。
#include <iostream> #include <memory> #include <cassert> class Object { public: Object(int id) : m_id(id) { std::cout << "init obj " << std::endl; } ~Object() { std::cout << "bye bye " << m_id << std::endl; } int id() const { return m_id; } private: int m_id; }; // 讓寫起來更方便 typedef std::shared_ptr<Object> ObjectPtr; typedef std::unique_ptr<Object> UniqueObjectPtr; // 只能傳遞引用 不能傳值 void print(const UniqueObjectPtr& obj) { std::cout << obj->id() << std::endl; } int main() { UniqueObjectPtr obj(new Object(1)); // UniqueObjectPtr obj1 = obj; // 編譯錯誤,不容許賦值 // 獲取原生指針 auto p = obj.get(); if( p ) { } else { } // better 重載了 operator bool if(obj) { } else { } // 重載了 operator -> 和 operator * std::cout << obj->id() << " " << (*obj).id() << std::endl; print(obj); // 釋放管理的指針,由其餘東西處理 p = obj.release(); // delete p; 本身負責處理 obj.reset(p); //析構以前負責管理的對象,從新管理 p指針 obj.reset(); // 析構以前負責管理的對象, 再也不管理任何資源 // 容許 移動操做 UniqueObjectPtr obj1(new Object(1)); // obj1已經部管理任何資源 obj2開始管理obj1以前的資源 UniqueObjectPtr obj2 = std::move(obj1); assert(obj1 == nullptr); std::cout << obj2->id() << std::endl; // 將unique_ptr管理的內容給 shared_ptr ObjectPtr obj3(std::move(obj2)); assert(obj2 == nullptr); } /* init obj 1 1 1 bye bye 1 init obj 1 bye bye 1 */
unique_ptr
管理數組資源不須要指定刪除器:
std::shared_ptr<int> p(new int[10], [](int* p){ std::cout << "delete[] p" << std::endl; delete[] p; //須要使用delete[] }); std::unique_ptr<int> p2(new int[10]); //不須要指定刪除器
使用智能指針雖然可以解決內存泄漏問題,可是也付出了必定的代價。以shared_ptr
舉例:
shared_ptr
的大小是原始指針的兩倍,由於它的內部有一個原始指針指向資源,同時有個指針指向引用計數。make_shared()
來避免,但也存在一些狀況下不可以使用make_shared()
。shared_ptr
可能調用了析構(所以所指向的資源的引用計數減一),同時,在另外一線程裏,指向相同對象的一個shared_ptr
可能執行了拷貝操做(所以,引用計數加一)。原子操做通常會比非原子操做慢。可是爲了線程安全,又不得不這麼作,這就給單線程使用環境帶來了沒必要要的困擾。我以爲仍是分場合吧,看應用場景來進行權衡,我也沒啥經驗,但我感受安全更重要,如今硬件已經足夠快了,其餘例如java
這種支持垃圾回收的語言不仍是用的很好嗎。
shared_ptr
很好用也很難用,有兩種構造方式,使用引用計數實現多人同時管理一份資源。使用this
的時候要格外注意。weak_ptr
能夠解決shared_ptr
的循環引用問題。unique_ptr
最像裸指針,但更爲安全,保證資源的釋放,不能複製只能移動。unique_ptr
,最後纔是shared_ptr
。