C++ 11 智能指針
前言:
近來,學習STL,突然發現有智能指針,做了一周的學習(工作之外的時間),斷斷續續的學習,特此做下記錄。
誕生的原因:
為了防止內存泄露,和二次釋放的問題。無非就是嫌棄自己管理內存太費勁,可以寫個更簡單管理堆內存的類。
利用C++的特性:
類結束會調用析構函數,無非就是棧空間出棧,同時釋放掉動態創建的空間。
智能指針的作用:
將指針封裝成類,利用了一種叫RAII(資源獲取即初始化的技術,聽着有點高大上),重載操作符(->和*),行為表現的像指針
- 防止多次釋放該指針,導致崩潰(前提是這個指針被你釋放是否賦值為空指針,如果賦值為空,就沒有所謂的崩潰問題,習慣決定代碼的健壯性)
- 智能指針作用把值語義轉為引用(總是搞個二傳手,這就是所謂的安全,降低性能為代價,在內存無比大的今天,隨意了)
C++ 11 中的智能指針:
包含在頭文件<memory>中,分別有三個智能指針,分別為shared_ptr,unique_ptr,weak_ptr。
- 介紹下shared_ptr(別人寫的很好,我只做網絡的搬運工,來豐富自己的知識體系)
1)原理:shared_ptr的多個對象指向同一個指針(大多是new出來的空間指針),該指針使用引用計數,每使用一次,內部計數器加1,每析構一次,內部的引用計數器減1,減為0的時候,自動刪除指向的堆內存。
2)實現:就是一個模板類,沒事的時候強烈建議看下里面的具體實現,挺有意思的。
3)不要用一個原始指針初始化多個shared_ptr,否則會造成二次釋放同一內存
4)注意避免循環使用,會在下面舉例,並講解。
2.unique_ptr
“唯一”擁有所指對象,同一時刻只能有一個unique_ptr指向給定對象(禁止使用拷貝語義,只能用移動語義將其移動)。對比原始指針,也是利用了RAII的特性。用戶可以定義delete操作。
#include <iostream> #include <memory> using namespace std; int main() { unique_ptr<int> uptr(new int(10)); //初始化 unique_ptr<int> uptr1 = move(uptr); //轉移所有權 uptr2.release(); //釋放所有權 return 0; }
3.weak_ptr,配合shared_ptr而引入的的智能指針,是弱引用,相對於shared_ptr強引用來說的。看似就像一個觀察者,觀測資源的使用情況。weak_ptr可以從一個shared_ptr獲取另一個weak_ptr來構造,獲取資源的觀察權 。 並不會引起計數加的情況,成員有use_count(),查看資源的引用計數,expired(),判斷是否指向的資源被釋放, 當返回為true的時候,這個資源的引用計數為0,相當於被釋放,反之就沒有被釋放掉。lock(),返回當前分享指 針,計數器並加1.
#include <iostream> #include <memory> using namespace std; int main() { shared_ptr<int> s_ptr = make_shared<int>(10); cout<<s_ptr.use_count() <<endl; weak_ptr<int> wp(s_ptr); cout<<wp.use_count() <<endl; if(!wp.expired()) { shared_ptr s_ptr2 = wp.lock(); //引用計數器加1 *s_ptr = 100; cout<<wp.use_count()<<endl; } } 運行結果: 1 1 2
應用場景及其問題:
1.循環引用,場景:考慮一個簡單的場景--家長和孩子,一個父母有一個孩子,一個孩子有一雙父母。
使用原始指針的實現:
#include <iostream> using namespace std; class Child; class Parent; class Parent { private: Child* myChild; public: void setChild(Child* ch) { this->myChild = ch; } void doSomething() { if(this->myChild) { cout<<"Child alive"<<endl; } } ~Parent() { cout<<"delete myChild"<<endl; delete myChild; } }; class Child { private: Parent* myParent; public: void setParent(Parent* p) { this->myParent = p; } void doSomething() { if(this->myParent) { cout<<"myParent alive"<<endl; } } ~Child() { cout<<"delete myParent"<<endl; delete myParent; } }; int main() { Parent* p = new Parent; Child* c = new Child; p->setChild(c); c->setParent(p); delete c; return 0; }
如何使用智能指針解決該問題呢:引入智能指針,兩個類只要保證一個類是shared_ptr(強引用)一個是weak_ptr(弱引用)
#include <memory> #include <iostream>
class Child; class Parent; class Parent{ private: std::weak_ptr<Child> ChildPtr; public: void setChild(std::shared_ptr<Child> child) { this->ChildPtr = child; } void doSomething() { } ~Parent() {} }; class Child { private: std::shared_ptr<Parent> ParentPtr; public: void setParent(std::shared_ptr<Parent> parent) { this->ParentPtr = parent; } void doSomething() {} ~Child() {} }; int main() { std::weak_ptr<Parent> wpp; std::weak_ptr<Child> wpc; { std::shared_ptr<Parent> p(new Parent); std::shared_ptr<Child> c(new Child); p->setChild(c); c->setParent(p); wpp = p; wpc = c; std::cout<<p.use_count() <<std::endl; std::cout<<c.use_count() <<std::endl; } std::cout <<wpp.use_count() << std::endl; std::cout << wpc.use_count() << std::endl; return 0; }
運行結果:
2
1
0
0
注意如果使用g++編譯,請添加參數-std=c++11(弱引用是C++11引入)
2..返回shared_ptr本身,並不引起計數器加+1
#include<iostream> #include<memory> using namespace std; class Test: public enable_shared_from_this<Test> { public: Test(){} ~Test() {cout <<"~Test()"<<endl;} shared_ptr<Test> sget() { return shared_from_this(); } }; int main() { weak_ptr<Test> wp; { shared_ptr<Test> sp(new Test); wp = sp; cout<<"sp is "<<sp.use_count()<<endl; sp->sget(); cout<<"sp is "<<sp.use_count()<<endl; } cout<<"wp is"<<wp.use_count()<<endl; return 0; return 0; } 運行結果: sp is 1 sp is 1 ~Test() wp is 0
3.注冊銷毀函數
#include<iostream> #include<memory> using namespace std; struct MyStruct { int *p; MyStruct():p(new int(10)){} }; int main() { MyStruct st; { shared_ptr<MyStruct> sp(&st, [](MyStruct *ptr){ delete(ptr->p); ptr->p = nullptr; cout<<"destructed"<<endl; }); } if(st.p != nullptr) cout<<"no destroyed"<<endl; else cout<<"be destroyed"<<endl; return 0; } 運行結果: destructed be destroyed
4.線程安全討論
官方文檔:1)同一個shared_ptr對象可以被多線程同時讀取
2) 不同的shared_ptr對象可以被多線程同時修改(提起12分注意力,這里有坑)
3)任何其他並發訪問的結果都是無定義的(什么軟,這個目前無法理解)
對1)來說,都能理解,讀一定是安全,當在2)情況下,由於內部shared_ptr有兩個成員,一個計數,一個指向
實際內存的指針,具體內部實現也沒有上鎖,同時操作兩個數據成員,讀寫操作無法做到原子化, 在多線程編程 中,在多個線程同時訪問同一個shared_ptr的時 候,請加mutex保護。
下面來詳細的分析下為什么:
1)首先看下shared_ptr內存結構,加入該指針指向一個Foo的類
2)考慮一個簡單的場景,有三個shared_ptr<Foo> 對象 x, g,n;
shared_ptr<Foo> g(new Foo); //線程之間共享的shared_ptr
shared_ptr<Foo> x; //線程A的局部變量
shared_ptr<Foo> n(new Foo);//線程B的局部變量
開始:還挺整齊的,符合我們的預想
線程A執行x = g;即(read g),以下圖示:但是還沒來的急將引用計數+1,切換到線程B
同時線程B執行g=n;(即寫g),如下圖
這個時候就已經將Foo1申請的動態內存歸還給操作系統了,出現空懸指針,如下圖:
最后將回到線程A,如下圖:
現在這個狀態,整個人都不好了。
多線程無保護的讀寫,造成了"x空懸指針"的后果,綜上,論證為啥對shared_ptr讀寫要加鎖的原因。
以上就是對智能指針的理解
具體參考:http://www.cnblogs.com/gqtcgq/p/7492772.html
vs2010中關於C++11 智能指針的源碼
23:37:57 2019-04-26