一、shared_ptr類
- 頭檔案:#include<memory>
- 智能指針,是一個模闆。建立智能指針時,必須提供指針所指的類型
- 如果當做前提條件判斷,則是檢測其是否為空
- shared_ptr<string> p1; //指向string
- shared_ptr<list<int>> p2;//指向int的list
- if(p1 && p1->empty())
- *p1="h1";
二、make_shared函數
- 最安全的配置設定和使用動态記憶體的方法就是調用該函數
- 此函數在記憶體中動态配置設定對象并初始化,傳回此對象的shared_ptr
//指向一個值為42的int的shared_ptr
shared_ptr<int> p = make_shared<int>(42);
//p2指向一個值為10個'9'的string
shared_ptr<string> p2=make_shared<string>(10, '9');
//p3指向一個值初始化為0的int數
shared_ptr<int> p3 = make_shared<int>();
- 配合auto使用:make_shared函數可以指派給auto,這樣比較簡單
auto p=make_shared<vector<string>>();
三、shared_ptr的拷貝、指派與引用計數
- 引用計數:shared_ptr類所指向的對象都有一個引用計數
- 但對shared_ptr類進行拷貝時,計數器就會增加。例如:當用一個shared_ptr初始化另一個shared_ptr、或者它作為參數傳遞給一個函數以及作為函數的傳回值,它所關聯的計數器就會增加
- 當我們給讓shared_ptr指向另一個對象或者shared_ptr銷毀時,原對象的計數器就會遞減
- 一旦一個shared_ptr的計數器為0,就會自動釋放該對象的記憶體
auto p=make_shared<int>(42); //p指向一個引用者
auto q(p); //用p初始化q,那麼p所指的對象計數器加1
- auto r=make_shared<int>(42);
- r=q;
- 将q指派給r,那麼:
- r原來所指的對象引用計數變為0,然後自動釋放記憶體
- q所指的對象的引用計數+1
四、shared_ptr的自動銷毀對象記憶體機制
- 由上面可知,當指向一個對象的最後一個shared_ptr對象被銷毀時,shared_ptr類會自動銷毀此對象。shared_ptr類是通過析構函數來完成銷毀工作的
- 記憶體浪費:因為隻有在銷毀掉最後一個shared_ptr時,該指針所指向的記憶體才會釋放,是以如果你忘記了銷毀程式不再需要的shared_ptr,程式仍然正在執行,那麼就造成記憶體浪費
五、shared_ptr與作用域的關系
- shared_ptr類所指向的記憶體何時被釋放,與shared_ptr類的生存周期有關
示範案例:
- 首先我們定義下面的函數傳回一個指向于一個值的share_ptr指針
- shared_ptr<Foo> factory(T arg)
- {
- return make_share<Foo>(arg);//傳回一個share_ptr類型的智能指針
- }
- 情景一:例如下面函數調用factory函數來生成一個shared_ptr指針,但是p一旦離開了作用域(use_factory函數),那麼p指針就失效了,是以p所指向的記憶體位址也就自動釋放了
- //函數結束之後,p就自動釋放它所指向的對象的記憶體
- void use_factory(T arg)
- {
- shared_ptr<Foo> p=factory(arg);
- }
- 情景二:下面的函數也是 factory函數來生成一個shared_ptr指針,但是p指針通過傳回值傳回了,是以,如果有另一個shared_ptr指針調用了該函數,那麼該p所指向的記憶體位址不會随着use_factory函數的調用而釋放
- auto use_factory(T arg)
- {
- shared_ptr<Foo> p=factory(arg);
- return p;
- }
六、shared_ptr與new的使用
使用規則:
- ①我們可以使用将shared_ptr類對象指向一個new所申請的動态記憶體
- ②new申請的動态記憶體的使用、釋放等規則仍然符合shared_ptr類的使用規則
使用文法:
- 因為智能指針的構造函數是explicit的。是以:我們不能将一個内置指針隐式地轉換為一個智能指針,必須使用直接初始化形式來初始化一個智能指針
- shared_ptr<int> p=new int(1024); //錯誤
- shared_ptr<int> p2(new int(1024)); //正确:使用直接初始化
- 動态記憶體作為傳回值時的使用手法:限于上面的使用文法,一個傳回shared_ptr的函數不能在其傳回語句中隐式轉換為一個普通指針
- shared_ptr<int> clone(int p)
- {
- return new int(p); //錯誤
- }
- shared_ptr<int> clone(int p)
- {
- return shared_ptr<int>(new int(p)); //正确
- }
七、shared_ptr類的函數傳參使用
- 當一個函數的參數是shared_ptr類時,有以下規則:
- 函數的調用是傳值調用
- 調用函數時,該shared_ptr類所指向的對象引用計數加1。但是函數調用完成之後,shared_ptr類自動釋放,對象的引用計數又減1
- void process(shared_ptr<int> ptr){ ... }
- shared_ptr<int> p(new int(42)); //初始化一個智能指針對象p
- process(p); //p所指的對象引用計數加1
- //process函數調用之後,p所指的引用計數減1
- int i=*p; //正确
函數參數使用時與new的關系:
- 因為shared_ptr類會在生存周期結束之後,将引用計數減1,當引用計數為0時,會釋放記憶體空間
- 下面是一個特殊的應用場景,需要注意
- void process(shared_ptr<int> ptr){ ... }
- int *x(new int(1024));
- process(x); //錯誤,不能将int*轉換為一個shared_ptr<int>
- process(shared_ptr<int>(x)); //合法的,但是process函數傳回之後記憶體會被釋放
- int j=*x; //錯誤,x所指的記憶體已經被釋放了
八、get函數的使用
- shared_prt類的get函數傳回一個内置指針,指向智能指針所管理的對象
- 此函數的設計情況:我們需要向不能使用智能指針的代碼傳遞一個内置指針
- get函數将記憶體的通路權限傳遞給一個指針,但是之後代碼不會delete該記憶體的情況下,對get函數的使用才是最安全的
- 永遠不要用get初始化另一個智能指針或者為另一個智能指針指派
- shared_ptr<int> p(new int(42)); //引用計數變為1
- int *q=p.get(); //正确:使用q需要注意,不要讓它管理的指針被釋放
- {//新語句塊
- shared_ptr<int>(q); //用q初始化一個智能指針對象
- } //語句塊結束之後,智能指針對象釋放它所指的記憶體空間
- int foo=*p;//錯誤的,p所指的記憶體已經被釋放了
九、reset、unique函數的使用
- reset函數會将shared_prt類原先所指的記憶體對象引用計數減1,并且指向于一塊新的記憶體
- shared_ptr<int> p;
- p=new int(1024); //錯誤:不能将一個指針賦予shared_ptr
- p=reset(new int(1034)); //正确,p指向一個新對象
- reset函數與unqie函數配合使用:在改變對象之前,檢查自己是否為目前對象的唯一使用者
- shared_ptr<string> p=make_shared<string>("Hello");
- if(!p.unique()) //p所指向的對象還有别的智能指針所指
- p.reset(new string(*p)); //現在可以放心的改變p了
- *p+=newVal; //p所指向的對象隻有自己一個智能指針,現在可以放心的改變對象的值了
十、異常處理
- 當程式發生異常時,我們可以捕獲異常來将資源被正确的釋放。但是如果沒有對異常進行處理,則有以下規則:
- shared_ptr的異常處理:如果程式發生異常,并且過早的結束了,那麼智能指針也能確定在記憶體不再需要時将其釋放
- new的異常處理:如果釋放記憶體在異常終止之後,那麼就造成記憶體浪費
- voif func()
- {
- shared_ptr<int> sp(new int(42));
- ...//此時抛出異常,未捕獲,函數終止
- }//shared_ptr仍然會自動釋放記憶體
- voif func()
- {
- int *ip=new int(42);
- ...//此時抛出異常,未捕獲
- delete ip; //在退出之前釋放記憶體,此語句沒有執行到,導緻記憶體浪費
- }
十一、重置shared_prt類删除器
- 概念:前面介紹過,當shared_ptr生命周期結束時,會調用預設的析構函數來釋放(delete)自己所指向的記憶體空間。但是我們可以使用shared_prt的文法來指定删除器函數,那麼在shared_ptr生命周期結束時就會自動調用這個函數
示範案例:
- 下面示範一個shared_ptr指定删除器函數以及避免記憶體洩露的案例
- 錯誤情景:我們調用f函數來打開一個網絡連接配接,但是在f函數調用之後沒有關閉這個連接配接。是以就會造成記憶體的洩露
- struct destination; //連接配接的對象
- struct connection; //連接配接需要的資訊
- connection connect(destbination*); //打開連接配接
- void disconnect(connection); //關閉連接配接
- void f(destination &d)
- {
- connection c=connect(&d);//打開一個連接配接
- ....//使用這個連接配接
- //如果在f函數退出之前忘記調用disconnect函數,那麼該連接配接就沒有關閉
- }
- 正确情景:現在我們定義一個新的函數“end_connection”,并且配合shared_ptr類的使用。shared_ptr指定了一個删除器函數“end_connection”。是以下面的代碼能夠保證在f函數的各種調用結束時,保證連接配接正确的關閉
- void end_connection(connection *p)
- {
- disconnection (*p);
- }
- void f(destination &d)
- {
- connection c=connect(&d);
- shared_ptr<connection> p(&c,end_connection);
- ....//使用這個連接配接
- //當f函數退出或者異常退出,p都會調用end_connection函數
- }
十二、shared_prt與動态數組的使用
- 與unique_ptr不同,shared_ptr不直接支援管理動态數組。如果希望使用shared_ptr管理動态數組,必須提供自己定義的删除器
- 如果未提供删除器,shared_ptr預設使用delete删除動态數組,此時delete少一個“[]”,因為會産生錯誤
//本例中,傳遞給shared_ptr一個lambda作為删除器
shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; } );
shared_ptr<int> sp2(new int[3]{1,2,3}, [](int *p) { delete[] p; });
sp2.reset(); //使用自己書寫的lambda釋放數組
- 動态數組的通路:shared_ptr不支援點和箭頭成員運算符通路數組,并且不提供下标運算符通路數組,隻能通過get()函數來擷取一個内置指針,然後再通路數組元素
shared_ptr<int> sp(new int[3]{1,2,3}, [](int *p) { delete[] p; });
for (size_t i = 0; i != 3; ++i)
*(sp.get() + i) = i;