c++ primer 第12章 - 動態內存

轉載地址:http://blog.csdn.net/wwh578867817/article/details/41866315

個人感覺此次只需要掌握 shared_ptr<string> sp = make_shared<string>("sdfk");

及其sp.use_count(); sp.unique();

第 12 章 動態內存


概述. 動態內存和智能指針

在c++中,動態內存的管理是通過一對運算符來完成的:

new,在動態內存中爲對象分配空間並返回一個指向該對象的指針。我們可以選擇對對象進行初始化

delete,接受一個動態對象的指針,銷燬該對象,並釋放與之相關的內存。

動態分配內存帶來了許多問題,比如忘記釋放的內存泄漏,提前釋放的指針非法訪問內存。

c++11新標準庫提供了兩種智能指針類型來管理動態對象,只能指針的行爲類似常規指針,區別是它自動釋放所指向的內存。

頭文件#include <memory>

兩種智能指針:

shared_ptr:允許多個指針指向同一個對象。

unique_ptr:獨佔所指向的對象。

伴隨類weak_ptr:指向share_ptr所管理的對象。


1.share_ptr類:ew plain c


//shared_ptr和unique_ptr都支持的操作  
//空智能指針。可以指向string類型的對象  
shared_ptr<string>sp;  
unique_ptr<string>up;  
sp         //sp可以作爲條件判斷sp是否指向一個對象  
*sp        //解引用sp,獲得它指向的對象  
sp->mem    //等價於(*sp).mem  
sp.get()   //返回sp中所報存的指針。要小心使用,所智能指針釋放了對象,則返回的指針所指向的對象也不存在了。  
swap(sp,sq)  
sp.swap(sq)  //交換sp和sq中的指針  
  
//shared_ptr支持的操作  
make_shared<T>(args)  //返回一個shared_ptr,指向一個動態分配的類型爲T的對象,使用args初始化對象  
shared_ptr<T>p(q)     //p是shared_ptr q的拷貝,此操作會遞增q中的記數器,q中的指針必須能轉換成T*  
p = q                 //p和q都是shared_ptr,所保存的指針必須能相互轉換,此操作會遞減p的引用計數,增加q的引用計數,p引用計數爲0時會釋放其管理的內存。  
p.use_count()         //返回與p共享智能指針的數量,可能很慢主要用於調試  
p.unique()            //當p.use_count()爲1時,返回ture,否則返回false

注意:

智能指針比較所指對象是否相同,只能通過get( )返回指針,比較指針的地址是否相等來判斷。


   <1.make_shared函數#include <memory>

make_shared是一個非成員函數,具有給共享對象分配內存,並且只分配一次內存的優點,和顯式通過構造函數初始化(new)的shared_ptr相比較,後者需要至少兩次分配內存。這些額外的開銷有可能會導致內存溢出的問題

最安全的使用動態內存的方法是使用一個make_shared的函數。

此函數在動態內存中分配一個對象並初始化,返回指向此對象的shared_ptr。

我們可以認爲每個shared_ptr都有一個關聯的計數器,通常稱其爲引用計數,無論我們拷貝一個share_ptr,計數器都會遞增。

當我們給一個shared_ptr賦值或者shared被銷燬,計數器就會遞減。

當用一個shared_ptr初始化另外一個shared_ptr,或將它作爲參數傳遞給一個函數以及作爲函數的返回值(賦值給其他的),計數器都會遞增

一旦一個share_ptr的計數器變爲0,它就會釋放自己所管理的對象。

!注意標準庫是用計數器還是其他數據結構來記錄有多少個指針共享對象由標準庫來決定,關鍵是智能指針類能記錄有多少個shared_ptr指向相同的對象,

並能在恰當的時候自動釋放對象。


補充:智能指針和make_shared分配內存初始化

#include <iostream>  
#include <string>  
#include <vector>  
#include <memory>  
  
using namespace std;  
  
int main()  
{  
    shared_ptr<string>sp;  
    make_shared<string>();         //動態分配內存默認初始化,必須要有括號  
    //make_shared<string>ms;       //error:動態分配內存!!聯想c語言malloc也沒有起名字 - -。  
    make_shared<string>("a");      //動態分配內存值初始化  
    shared_ptr<string>sp2 = make_shared<string>();   //初始化智能指針  
    shared_ptr<string>sp3 = make_shared<string>("b");//初始化智能指針  
}  



例子:
[cpp] view plain copy
#include <iostream>  
#include <memory>  
#include <string>  
  
using namespace std;  
  
shared_ptr<string> fun1(shared_ptr<string> sp5)//傳遞參數會構造一個,計數器遞增,函數運行結束後釋放  
{  
    auto sp6 = sp5;                            //創建臨時並賦值,計數器遞增。  
    cout << "sp5 use_count:" << sp5.use_count() << endl;  
    cout << "sp5 is_unique:" << sp5.unique() << endl;  
    return sp6;  
}                                                
  
int main()  
{  
    shared_ptr<string>sp = make_shared<string>("aa");  
    auto sp3 = make_shared<string>(10,'a');//通常使用auto來簡化定義一個對象來保存make_shared的結果,這種方式比較簡單。  
    cout << "sp use_count:" << sp.use_count() << endl;  
    auto sp2(sp);                          //拷貝sp,count計數會增加  
    cout << "sp use_count:" << sp.use_count() << endl;  
    cout << "sp is_unique:" << sp.unique() << endl;  
    sp2 = sp3;                             //賦值sp2,計數會減少  
    cout << "sp use_count:" << sp.use_count() << endl;  
    cout << "sp is_unique:" << sp.unique() << endl;  
    auto sp4(sp3);  
    cout << "sp3 use_count:" << sp3.use_count() << endl;  
    cout << "sp3 is_unique:" << sp3.unique() << endl;  
    sp = sp3;//sp指向sp3指向的,sp指向的被銷燬。  
    cout << "sp use_count:" << sp.use_count() << endl;  
    cout << "sp is_unique:" << sp.unique() << endl;  
    auto sp7 = fun1(sp);                     
    cout << "sp7 use_count:" << sp.use_count() << endl;  
    cout << "sp7 is_unique:" << sp.unique() << endl;  
      
}  
 view plain cop

注意! sp1 = sp2;  sp2計數器值增加,右值的計數器增加,左值指向的對象的計數器減少,減少爲0時自動釋放對象


<2.shared_ptr 自動銷燬所管理的對象

當指向對象的最後一個shared_ptr 被銷燬時,shared_ptr 類會自動銷燬此對象。它是通過特殊的成員函數析構函數來控制對象銷燬時做什麼操作。

shared_ptr 的析構函數會遞減它所指向的對象的引用計數,如果引用計數變爲0,shared_ptr 的函數就會銷燬對象,並釋放它佔用的資源。

對於一塊內存,shared_ptr 類保證只要有任何shared_ptr 對象引用它,它就不會被釋放。

如果我們忘記了銷燬程序不再需要的shared_ptr,程序仍然會正確運行,但會浪費內存

注意!:如果你將shared_ptr存放於一個容器中,而後不在需要全部元素,而只使用其中的一部分,要記得調用erase刪除不再需要的那些元素。

注意!:將一個shared_ptr 賦予另一個shared_ptr 會遞增賦值號右側的shared_ptr 的引用計數,而遞減左側shared_ptr 的引用計數,如果一個shared_ptr 引用技術

變爲0時,它所指向的對象會被自動銷燬。


<3.使用了動態生存期的資源的類

程序使用動態內存出於以下三種原因

1.程序不知道自己需要使用多少對象

2.程序不知道所需對象的準確類型

3.程序需要在多個對象間共享數據。

使用動態內存的一個常見的原因是允許多個對象共享相同的狀態。


重點例子!!

我們希望定義一個Blob類,保存一組元素,與容器不同,我們希望Blob對象的不同拷貝之間共享相同的元素。既當我們拷貝一個Blob時,

原Blob對象及其拷貝應該引用相同的底層元素。

定義一個管理string的類,命名爲StrBlob。

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <string>  
  3. #include <memory>             //智能指針和動態分配內存  
  4. #include <vector>  
  5. #include <initializer_list>   //初始值列表  
  6. #include <stdexcept>  
  7.   
  8. class StrBlob  
  9. {  
  10.     public:  
  11.         typedef std::vector<std::string>::size_type size_type;  
  12.         StrBlob();  
  13.         StrBlob(std::initializer_list<std::string>il);  
  14.         size_type size()constreturn data->size(); }  
  15.         bool empty() { return data->empty(); }  
  16.         //添加刪除元素  
  17.         void push_back(const std::string &s){ data->push_back(s); }  
  18.         void pop_back();  
  19.         //訪問元素  
  20.         std::string& front();  
  21.         std::string& back();  
  22.         const std::string& front()const;   
  23.         const std::string& back() const;  
  24.   
  25.     private:  
  26.         std::shared_ptr<std::vector<std::string>> data;  
  27.         //private 檢查函數。  
  28.         void check(size_type i, const std::string &msg)const;  
  29. };  
  30.   
  31. //默認構造函數  
  32. StrBlob::StrBlob():  
  33.     data(std::make_shared<std::vector<std::string>>()) { }  
  34. //拷貝構造函數  
  35. StrBlob::StrBlob(std::initializer_list<std::string>il):  
  36.     data(std::make_shared<std::vector<std::string>>(il)) { }  
  37.   
  38.   
  39. void StrBlob::check(size_type i, const std::string &msg)const  
  40. {  
  41.     if(i >= data->size())  
  42.         throw std::out_of_range(msg);  
  43. }  
  44.   
  45. const std::string& StrBlob::back()const  
  46. {  
  47.     check(0, "back on empty StrBlob");  
  48.     return data->back();  
  49. }  
  50. <span style="color:#FF0000;">  
  51. //避免代碼重複和編譯時間問題,用non-const版本調用const版本  
  52. //在函數中必須先調用const版本,然後去除const特性  
  53. //在調用const版本時,必須將this指針轉換爲const,注意轉換的是this指針,所以<>裏面是const StrBlob* 是const的類的指針。  
  54. //調用const版本時對象是const,所以this指針也是const,通過轉換this指針才能調用const版本,否則調用的是non-const版本,non-const調用non-const會引起無限遞歸。  
  55. //return時,const_cast拋出去除const特性。</span>  
  56.   
  57. std::string& StrBlob::back()  
  58. {  
  59.     const auto &s = static_cast<const StrBlob*>(this)->back(); //<span style="color:#FF0000;">auto前面要加const,因爲auto推倒不出來const。</span>  
  60.     return const_cast<std::string&>(s);  
  61. }  
  62.   
  63. const std::string& StrBlob::front()const  
  64. {  
  65.     check(0, "front on empty StrBlob");  
  66.     return data->front();  
  67. }  
  68.   
  69. std::string& StrBlob::front()  
  70. {  
  71.     const auto &s = static_cast<const StrBlob*>(this)->front();  
  72.     return const_cast<std::string&>(s);  
  73. }  
  74.   
  75. void StrBlob::pop_back()  
  76. {  
  77.     check(0, "pop_back on empty StrBlob");  
  78.     data->pop_back();  
  79. }  
  80.   
  81. int main()  
  82. {  
  83.     std::shared_ptr<StrBlob>sp;  
  84.     StrBlob s({"wang","wei","hao"});  
  85.     StrBlob s2(s);//共享s內的數據  
  86.     std::string st = "asd";  
  87.     s2.push_back(st);  
  88.     //s2.front();  
  89.     std::cout << s2.front() << std::endl;  
  90.     std::cout << s2.back() << std::endl;  
  91. }  

可以輸出s和s2的size( )是相等的證明他們共享的是同一塊內存。


2.直接管理內存

<1.使用new動態分配和初始化對象

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <string>  
  3. #include <vector>  
  4.   
  5. using namespace std;  
  6.   
  7. int main()  
  8. {  
  9.     //在自由空間分配的內存是無名的,因此new無法爲其分配的對象命名,而是返回該指向該對象的一個指針。  
  10.     int *p = new int;  
  11.     int *p2 = new int(10);  
  12.     string *p3 = new string(10,'a');  
  13.     vector<string> *p4 = new vector<string>{"a","b","c"};  
  14.     //也可以對動態分配的對象進行值初始化,只需在類型名之後加一對空括號  
  15.     string *p5 = new string(); //值初始化  
  16.     string *p6 = new string;   //默認初始化  
  17.     int *p7 = new int;         //但是對於內置類型是未定義的。*p7值未定義  
  18.     //對動態分配的對象進行初始化通常是個好主意。  
  19.   
  20.     //auto   
  21.     auto p8 = new auto("abc");  
  22.     auto p9 = new auto{1,2,3}; //error  ??  
  23.   
  24.     //const  
  25.     //一個動態分配的const對象必須進行初始化。  
  26.     const string *p10 = new const string("aha");  
  27.   
  28.     //內存耗盡  
  29.     int *p11 = new int;  //如果分配失敗,new會拋出一個std::bad_alloc  
  30.     int *p12 = new (nothrowint//如果分配失敗返回一個空指針。 bad_alloc和nothrow定義在#include <new>  
  31. }  

<2.delete注意:

delete p;釋放p所指向的對象的那塊內存區域,釋放後p仍然指向那塊區域(測試時輸出的地址仍然相同),但是釋放後輸出的對象已無效。

一般我們可以在釋放delete p後, p = nullptr。這樣明確指針不指向其他的區域。

空懸指針:指向一塊曾經保存數據現在已經無效的內存的指針。


堅持只使用智能指針,就可以避免所有的問題,對於一塊內存,只有在沒有任何智能指針指向它的情況下,智能指針纔會釋放它。

[cpp] view plain copy
  1. #include <memory>  
  2. #include <iostream>  
  3.   
  4. using namespace std;  
  5.   
  6. int main()  
  7. {  
  8.     //p也指向p2指向的內存,那麼p原來所指向的內存區域就沒有其他指針指向了,也沒有釋放,內存泄漏  
  9.     int *p = new int(20);  
  10.     int *p2 = new int(40);  
  11.     p = p2;  
  12.     //p3也指向p4所指向的內存,但是p3原先指向的內存區域計數器變爲0時,內存自動會釋放。  
  13.     auto p3 = make_shared<int>(20);  
  14.     auto p4 = make_shared<int>(30);  
  15.     p3 = p4;  
  16. }  


<3.shared_ptr和new結合使用。

<<1.

可以用new來初始化智能指針

接受指針參數的智能指針構造函數是explicit(避免隱式轉換)的,我們不能將一個內置指針隱式轉換爲一個智能指針。


默認情況下,一個用來初始化智能指針的普通指針必須指向一塊動態分配的內存,因爲智能指針默認使用delete釋放它所關聯的對象。

如果將智能指針綁定到其他類型的指針上,我們必須自己定義自己的釋放操作。

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <memory>  
  3.   
  4. using namespace std;  
  5.   
  6. shared_ptr<int> clone(int p)  
  7. {  
  8.     return shared_ptr<int>(new int(p));  
  9.     //return new int(p);   error  
  10. }  
  11.   
  12. int main()  
  13. {  
  14.     shared_ptr<int>p(new int(10));  
  15.     cout << "p:" << *p << endl;  
  16.     cout << "p:" << p.unique() << endl;  
  17.     cout << "p:" << p.use_count() << endl;  
  18.     //error: not int* transfrom shared_ptr<int>  
  19.     //shared_ptr<int>p2 = new int(10);  
  20.   
  21.     //定義和改變shared_ptr的其他方法  
  22.     int *p2 = new int(20);  
  23.     shared_ptr<int>p3(p2);  
  24.     cout << "p3:" << *p3 << endl;  
  25.     cout << "p3:" << p3.unique() << endl;  
  26.     cout << "p3:" << p3.use_count() << endl;  
  27.     unique_ptr<int>p4(new int(40));  
  28.     //shared_ptr<int>p5(move(p4));//要添加move將p4轉換爲右值。左值不行。  
  29.     //cout << "p5:" << p5.unique() << endl;  
  30.     //cout << "p5:" << p5.use_count() << endl;  
  31.     p3.reset();//重置p3  
  32.     int *p6 = new int(50);  
  33.     p3.reset(p6);//重置p3並將p3綁定到p6。  
  34.     cout << "p3:" << *p3 << endl;  
  35.     cout << "p3:" << p3.unique() << endl;  
  36.     cout << "p3:" << p3.use_count() << endl;  
  37.   
  38.     //shared_ptr<T> p(q,d); d自己定義delete釋放內存  
  39.     //shared_ptr<T> p(p2,d);  
  40.     //p.reset();    釋放p對象,use_count()減了1。  
  41.     //p.reset(q);   釋放p對象,獲得q,q必須是內置類型動態分配的!  
  42.     //p.reset(q,d); 用d函數釋放p對象,獲得q, (q必須是內置類型動態分配的!)。  
  43.   
  44. }  


<<2.不要混用智能指針和普通指針。

shared_ptr 可以協調對象的析構(也就是計數爲0釋放),僅限於自身的拷貝,所以推薦使用make_shared而不是new。

在分配對象時就將對象綁定在shared_ptr上面。

當將一個shared_ptr 綁定到一個普通指針時,我們就將內存管理交給了shared_ptr,之後我們就不應該使用內置指針來訪問shared_ptr指向的內存了。

使用內置指針來訪問智能指針所附則的對象是非常危險的,我們不知道對象何時被銷燬。


<<3.也不要使用get初始化另一個智能指針或者爲智能指針賦值

智能指針定義了一個名爲get的函數,返回一個普通類型的指針。

目的:向不能使用智能指針的代碼傳遞一個內置指針,使用get返回的指針的代碼不能delete此指針。

將另一個智能指針綁定到get返回的指針也是錯誤的。

永遠不要用get初始化另一個智能指針或者爲另一個智能指針賦值。

普通指針不能自動轉化爲智能指針。

爲什麼使用get返回的指針不能使用delete

[cpp] view plain copy
  1. {  
  2.     auto p = make_shared<int>(20);  
  3.     auto q = p.get();  
  4.     delete q;  
  5. }  
p是一個智能指針,在函數塊結束後會自動調用內部delete釋放動態申請的空間,然而我們又delete了一次,等於釋放了兩次空間。

*** Error in `./a.out': double free or corruption (out): 0x00007fff21931150 ***

書上的一個例子,有點問題記錄下

[cpp] view plain copy
  1. #include <memory>  
  2. #include <iostream>  
  3.   
  4. using namespace std;  
  5.   
  6. int main()  
  7. {  
  8.     shared_ptr<int>p(new int(42));  
  9.     int *q = p.get();  
  10.     cout << "count:" << p.use_count() << endl;  
  11.     //delete q; error:  
  12.     {  
  13.         auto t = shared_ptr<int>(q); //轉換      
  14.         cout << "count:" << t.use_count() << endl;  
  15.     }  
  16.     int foo = *p;  
  17.     cout << foo << endl;  
  18. }  
書上沒有auto t,按照書上foo可以正常使用,估計是編譯器優化。

書上意思是智能指針t也指向q所指向的內存,但是引用計數都是1,函數塊結束後,t被釋放,內存也被delete,那麼p指向的未定義了。


<<4.智能指針和異常

使用智能指針,即使程序塊過早結束,智能指針類也能確保內存不再需要時將其釋放。

但是普通指針就不會

[cpp] view plain copy
  1. void fun( )  
  2. {  
  3.         int *p = new int(42);  
  4.         //如果這時拋出一個異常且未捕獲,內粗不會被釋放,但是智能指針就可以釋放。  
  5.         delete p;  
  6. }  

<<5.智能指針和啞類

標準很多都定義了析構函數,負責清理對象使用的資源,但是一些同時滿足c和c++的設計的類,通常都要求我們自己來釋放資源。

通過智能指針可以很好的解決這個問題

舉個網絡連接的例子:

[cpp] view plain copy
  1. connection connect(*destination);  
  2. void disconnect(connect);  
  3. void f(destination &d)  
  4. {  
  5.         connection c  = connect(&d);  
  6.         disconnect(d);//如果沒有調用disconnect,那麼永遠不會斷開連接。  
  7. }  

[cpp] view plain copy
  1. //使用智能指針優化,等於自己定義了delete代替本身的delete  
  2. connection connect(*destination);  
  3. void end_disconnect(connection*p) {disconnect(p);}   
  4. void f(destination &d)  
  5. {  
  6.         connection c = connect(&d);  
  7.         shared_ptr<connection>p(&d, end_connect);  
  8.         //f退出時,會自動調用end_connect。  
  9. }  

demo,用string代替connection類型。

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <string>  
  3. #include <memory>  
  4.   
  5. using namespace std;  
  6. typedef string connection;  
  7.   
  8. connection& connect(connection *s)  
  9. {  
  10.     cout << "正在連接..." << endl;  
  11.     s = new connection("connect");  
  12.     return *s;  
  13. }  
  14.   
  15. void disconnect(connection *s)  
  16. {  
  17.     cout << "正在斷開連接..." << endl;  
  18. }  
  19.   
  20. int main()  
  21. {  
  22.     connection p;  
  23.     connection *d;  
  24.     p = connect(d);  
  25.     shared_ptr<connection>sp(&p,disconnect);//&p  
  26. }  

這樣做即使我們忘記寫斷開連接或者中間發生了異常都會保證執行斷開連接的代碼。


!注意:智能指針陷阱

*不使用相同的內置指針值初始化(或reset)多個智能指針    //多個智能指針還是單獨的指向內置指針的內存,use_count分別爲1

*不delete get( )返回的指針                                                           //兩次delete釋放,智能指針內部也會delete

*不使用get( )初始化或reset另一個智能指針                               //free( ): invalid pointer:也是多次釋放

*如果你使用get( )返回的指針,記住當最後一個對應的智能指針銷燬後,你的指針就變得無效了

*如果你使用智能指針管理的資源不是new分配的內存,記住傳遞給它一個刪除器(刪除函數向上面的disconnect( ))。


課後題12.15

使用lambda改寫connect函數。

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <string>  
  3. #include <memory>  
  4. #include <functional>  
  5. #include <algorithm>  
  6.   
  7. using namespace std;  
  8. typedef string connection;  
  9.   
  10. connection& connect(connection *s)  
  11. {  
  12.     cout << "正在連接..." << endl;  
  13.     s = new connection("connect");  
  14.     return *s;  
  15. }  
  16.   
  17. void disconnect(connection *s)  
  18. {  
  19.     cout << "正在斷開連接..." << endl;  
  20. }  
  21.   
  22. int main()  
  23. {  
  24.     connection p;  
  25.     connection *d;  
  26.     p = connect(d);  
  27.     //shared_ptr<connection>sp(&p,disconnect);      
  28.     //error:lambda代表了刪除函數。那麼參數列表也要和刪除函數一致,因爲delete內部是free(p)。  
  29.     //shared_ptr<connection>sp(&p, [&p] { disconnect(&p); });   
  30.     shared_ptr<connection>sp(&p,   [](connection *s) { disconnect(s); });  
  31. }  


shared_ptr 的傳遞刪除器(deleter)方式比較簡單, 只需要在參數中添加具體的刪除器函數名, 即可; 注意是單參數函數;
unique_ptr 的刪除器是函數模板(function template), 所以需要在模板類型傳遞刪除器的類型(即函數指針(function pointer)), 再在參數中添加具體刪除器;


2.unique_ptr

介紹:一個unique_ptr 擁有它所指向的對象,和shared_ptr不同,某個時刻只能有一個unique_ptr 指向一個給定對象,當unique_ptr 被銷燬時,對象也被銷燬

<1.

unique沒有類似make_shared,必須手動new,將其綁定

由於unique_ptr獨佔它所指向的對象,因此他不支持普通的拷貝和賦值

但是有種特殊的拷貝可以支持:我們可以拷貝或賦值一個即將要被銷燬的unique_ptr。

[cpp] view plain copy
  1. #include <memory>  
  2. #include <iostream>  
  3. #include <string>  
  4.   
  5. using namespace std;  
  6.   
  7. unique_ptr<int> clone(int p)  
  8. {  
  9.     unique_ptr<int>q(new int(p));  
  10.     return q;  
  11.     //return unique<int>q(new int(p));  
  12. }  
  13.   
  14. int main()  
  15. {  
  16.     unique_ptr<string>p(new string("aaa"));  
  17.     shared_ptr<string>p2(new string("aaa"));  
  18.     //unique_ptr<string>p3(p);   error:不能拷貝  
  19.     //unique_ptr<string>p4 = p;  error:不能賦值  
  20.     unique_ptr<string>p5;  
  21.     string s = "a";  
  22.     //p5.reset(&s);              error:兩次釋放  
  23.   
  24.     //兩種轉移所有權的方法  
  25.     unique_ptr<string>p6(p.release());//p.release(),釋放p對指針對象的控制權,返回指針並將p置空,並不會釋放內存  
  26.     unique_ptr<string>p7;  
  27.     p7.reset(p6.release());           //p6釋放控制權,p7指向這個對象。  
  28.   
  29.     //特殊的拷貝和賦值  
  30.     int i = 10;  
  31.     clone(i);  
  32. }  

在早的版本中提供了auto_ptr的類,它有unique_ptr 的部分特性,但是不能在容器中保存auto_ptr, 也不能在函數中返回 auto_ptr, 編寫程序時應該使用unique_ptr.


<2.向unique_ptr 傳遞刪除器

類似shared_ptr, unique_ptr 默認情況下用delete釋放它指向的對象,和shared_ptr 一樣我們可以重載一個unique_ptr 中默認的刪除器類型。

重載一個unique_ptr 中的刪除器會影響到unique_ptr 類型及如何構造該類型的對象,

我們必須在尖括號中unique_ptr 指向類型之後提供刪除器的類型,在創建或reset一個這種unique_ptr類型的對象時,必須提供一個指定類型的可調用對象(刪除器)

[cpp] view plain copy
  1. #include <memory>  
  2. #include <iostream>  
  3.   
  4. using namespace std;  
  5.   
  6. typedef int connection;  
  7.   
  8. connection* connect(connection *d)  
  9. {  
  10.     cout << "正在連接..." << endl;  
  11.     d = new connection(40);  
  12.     return d;  
  13. }  
  14.   
  15. void disconnect(connection *p)  
  16. {  
  17.     cout << "斷開連接..." << endl;  
  18. }  
  19.   
  20. int main()  
  21. {  
  22.     connection *p,*p2;  
  23.     p2 = connect(p);  
  24.     cout << p << endl;   
  25.     cout << *p2 << endl;  
  26.     unique_ptr<connection, decltype(disconnect)*>q(p2,disconnect);  
  27.     //在尖括號中提供類型,圓括號內提供尖括號中的類型的對象。  
  28.     //使用decltype()關鍵字返回一個函數類型,所以必須添加一個*號來指出我們使用的是一個指針  
  29. }  

注意:

p.release( );                   //error:p2不會釋放內存,而且丟失了指針

auto q = p.release( );   //q 是int * 類型, 記得delete釋放q


c++11

3.weak_ptr

weak_ptr 是一種不控制對象生存期的智能指針,它指向由一個shared_ptr 管理的對象。

[cpp] view plain copy
  1. #include <memory>  
  2. #include <iostream>  
  3.   
  4. using namespace std;  
  5.   
  6. int main()  
  7. {  
  8.     shared_ptr<int>p0 = make_shared<int>(42);  
  9.     auto p1 = p0;  
  10.     cout << "p1 count:" << p1.use_count() << endl;  
  11.     weak_ptr<int>p2;  
  12.     weak_ptr<int>p3(p1);  //與p1指向相同的對象  
  13.     p3 = p0;              //與p0指向相同的對象  
  14.     p2 = p3;  
  15.     cout << "p2 count:" << p2.use_count() << endl;  
  16.     //w.reset() 將w置空  
  17.     p2.reset();           //將p2置空  
  18.     cout << "p2 count:" << p2.use_count() << endl;  
  19.     cout << "p3 count:" << p3.use_count() << endl;  
  20.     //w.expired() 若w.use_count()爲0,返回true,否則返回false    expired(過期的)  
  21.     //w.expried() 也就是判斷shared_ptr的count爲0嗎。  
  22.     cout << "p2 expired:" << p2.expired() << endl;  
  23.     cout << "p3 expired:" << p3.expired() << endl;  
  24.     //w.lock()    如果expired爲true,返回一個空的shared_ptr,否則返回一個指向w的對象的shared_ptr  
  25.     //w.lock()    也就是如果shared_ptr的count爲0,返回空,不爲0,返回shared_ptr。  
  26.     auto p4 = p2.lock();  
  27.     cout << "p4 count:" << p4.use_count() << endl;  
  28.     auto p5 = p3.lock();//引用計數加1  
  29.     cout << "p5 count:" << p5.use_count() << endl;  
  30. }  

!注意:

當我們創建一個weak_ptr 必須用一個 shared_ptr 初始化。

引入lock和expired是防止在weak_ptr 不知情的情況下,shared_ptr 被釋放掉

weak_ptr 不會更改shared_ptr 的引用計數。

std::weak_ptr 是一種智能指針,它對被 std::shared_ptr 管理的對象存在非擁有性(“弱”)引用。在訪問所引用的對象前必須先轉換爲 std::shared_ptr

std::weak_ptr 用來表達臨時所有權的概念:當某個對象只有存在時才需要被訪問,而且隨時可能被他人刪除時,可以使用std::weak_ptr 來跟蹤該對象。需要獲得臨時所有權時,則將其轉換爲 std::shared_ptr,此時如果原來的 std::shared_ptr被銷燬,則該對象的生命期將被延長至這個臨時的 std::shared_ptr 同樣被銷燬爲止。

此外,std::weak_ptr 還可以用來避免 std::shared_ptr 的循環引用。



例子:

定義一個StrBlobPtr(內部weak_ptr)類打印StrBlob中的元素

上面的StrBlobPtr 例子的改進。

StrBlobPtr內部是weak_ptr實現的,它實際上就是一個助手類,作用就是類似一個旁觀者,一直觀測StrBlob的資源使用情況

[cpp] view plain copy
  1. /*  
  2.  *避免拷貝,多個指針共用一個vector<string> 
  3.  *使用weak_ptr訪問共享的對象 
  4.  * */  
  5.   
  6. #include <iostream>  
  7. #include <vector>  
  8. #include <string>  
  9. #include <initializer_list>  
  10. #include <memory>  
  11. #include <stdexcept>  
  12. #include <fstream>  
  13. #include <sstream>  
  14.   
  15. class StrBlob;  
  16. class StrBlobPtr;  
  17.   
  18.   
  19. class StrBlob  
  20. {  
  21.     public:  
  22.         friend class StrBlobPtr;  
  23.         typedef std::vector<std::string>::size_type size_type;  
  24.         StrBlob(); //默認構造函數  
  25.         StrBlob(std::initializer_list<std::string>il); //拷貝構造函數  
  26.         size_type size() { return data->size(); }      //對data進行解引用就是對vector<string>操作  
  27.         std::string& front();  
  28.         std::string& back();  
  29.         const std::string& front()const;  
  30.         const std::string& back()const;  
  31.         void push_back(const std::string &s) { data->push_back(s); }  
  32.         void pop_back();  
  33.         //StrBlobPtr begin() { return StrBlobPtr(*this); }  
  34.         //StrBlobPtr end() { auto ret = StrBlobPtr(*this, data->size());    
  35.         //                   return ret; }  
  36.   
  37.     private:  
  38.         void check(size_type sz, std::string msg)const;  
  39.         std::shared_ptr<std::vector<std::string>>data;  
  40. };  
  41.   
  42. std::string& StrBlob::front()  
  43. {  
  44.     const auto &s = static_cast<const StrBlob*>(this)->front();  
  45.     return const_cast<std::string&>(s);  
  46. }  
  47.   
  48. std::string& StrBlob::back()  
  49. {  
  50.     const auto &s = static_cast<const StrBlob*>(this)->back();  
  51.     return const_cast<std::string&>(s);  
  52. }  
  53.   
  54. const std::string& StrBlob::front()const  
  55. {  
  56.     check(0, "front on empty vector");  
  57.     return data->front();  
  58. }  
  59.   
  60. const std::string& StrBlob::back()const  
  61. {  
  62.     check(0, "back on empty vector");  
  63.     return data->back();  
  64. }  
  65.   
  66. void StrBlob::check(size_type sz, std::string msg)const  
  67. {  
  68.     if(sz >= data->size())  
  69.         throw std::out_of_range(msg);  
  70. }  
  71.   
  72. StrBlob::StrBlob():  
  73.     data(std::make_shared<std::vector<std::string>>()) { }  
  74.   
  75. StrBlob::StrBlob(std::initializer_list<std::string> il):  
  76.     data(std::make_shared<std::vector<std::string>>(il)) { }  
  77.   
  78. /* --------------------------------------------------------------------------------- */  
  79.   
  80. //必須定義在StrBlobPtr的後面  
  81. //否則error: invalid use of incomplete type ‘class StrBlob’  
  82. class StrBlobPtr  
  83. {  
  84.     public:  
  85.         friend StrBlob;  
  86.         StrBlobPtr():curr(0){ }  
  87.         StrBlobPtr(StrBlob &s, std::size_t sz = 0):   
  88.             wptr(s.data), curr(sz){ }  
  89.         std::string& deref()const;//返回當前string  
  90.         StrBlobPtr& incr(); //遞增  
  91.   
  92.     private:  
  93.         std::shared_ptr<std::vector<std::string>> check(std::size_t i, const std::string &msg)const;  
  94.         std::weak_ptr<std::vector<std::string>> wptr;  
  95.         std::size_t curr;  //當前下標  
  96. };  
  97.   
  98. StrBlobPtr& StrBlobPtr::incr()  
  99. {  
  100.     check(curr, "increment past end of StrBlobPtr");  
  101.     ++curr; //推進當前位置。    
  102.     return *this;                    //爲什麼要return *this, 如果再次自加可以重複,舉個例子就像賦值一樣 a = b = c;  如果不返回對象不能繼續賦值。  
  103. }                                    //return *this是一份拷貝。 return this是地址。  
  104.   
  105. std::string& StrBlobPtr::deref()const  
  106. {  
  107.     auto p = check(curr, "dereference past end"); //shared_ptr引用計數會增加,但是作用域結束後,引用計數又會減1  
  108.     return (*p)[curr];    //p是所指的vector  
  109. }  
  110.   
  111. //check檢查是否存在shared_ptr和大小  
  112. std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string &msg)const  
  113. {  
  114.     auto ret = wptr.lock(); //檢查是否存在,存在返回shared_ptr,不存在返回空的shared_ptr.  
  115.     if(!ret)  
  116.         throw std::runtime_error("unbound StrBlobPtr");  
  117.     if(i >= ret->size())  
  118.         throw std::out_of_range(msg);  
  119.     return ret;  
  120. }  
  121.   
  122.   
  123. int main(int argc, char*argv[])  
  124. {  
  125.     std::fstream is(argv[1]);  
  126.     std::string s;  
  127.     StrBlob S;  
  128.     while(std::getline(is, s))  
  129.     {  
  130.         std::string temp;  
  131.         std::istringstream ist(s);  
  132.         while(!ist.eof())  
  133.         {  
  134.             ist >> temp;  
  135.             S.push_back(temp);  
  136.         }  
  137.     }  
  138.   
  139.     std::cout << "size:" << S.size() << std::endl;  
  140.   
  141.     StrBlobPtr sp(S);  
  142.     for(auto i = 0; i < S.size(); ++i)  
  143.     {  
  144.         std::cout << sp.deref() << std::endl;  
  145.         sp.incr();  
  146.     }  
  147. }  



4.動態數組

new和delete一次只能分配和釋放一個對象,但有時我們需要一次爲很多對象分配內存的功能

C++和標準庫引入了兩種方法,另一種new 和 allocator。

使用allocator 通常會提供更好的性能和更靈活的內存管理能力。

<1.new和數組

動態數組不是數組類型。

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <memory>  
  3.   
  4. using namespace std;  
  5.   
  6. typedef int arr[10];  
  7.   
  8. int main()  
  9. {  
  10.     int *p = new int[10];  
  11.     int *p2 = new arr;  
  12.     for(int i = 0; i < 10; i++)  
  13.     {  
  14.         p[i] = i;  
  15.     }  
  16.     for(int i = 0; i < 10; i++)  
  17.         cout << p[i] << " ";  
  18.     cout << endl;  
  19.     //for(const int i : p);         //error:動態分配數組返回的不是數組類型,而是數組元素的指針。所以不能用範圍for  
  20.       
  21. /*---------------------------------- */  
  22.     //初始化動態數組  
  23.     int *pi = new int[10];          //未初始化  
  24.     int *pi2 = new int[10]();       //初始化爲0,且有括號必須爲空  
  25.     string *ps = new string[10];       //10個空string  
  26.     string *ps2 = new string[10]();    //10個空string  
  27.     //可以使用列表初始化  
  28.     int *pi3 = new int[10]{1,2,3,4,5,6,7,8,9,0};//初始值列表裏的值不能多於容量,否則new失敗,不會分配內存  
  29.     string *ps3 = new string[10]{"a","b","c","d","e","f","g","h","i",string(3,'x')};  
  30.     //釋放動態數組  
  31.     delete []pi3; //必須要加[]括號,且釋放動態數字時是逆序釋放。如果delete動態數組不加[],行爲是未定義的。  
  32.   
  33. /*----------------------------------- */  
  34.     //智能指針和動態數組,標準庫定義了特別的unique_ptr來管理,當uo銷燬它管理的指針時,會自動調用delete [];  
  35.     int *p5 = new int[10];  
  36.     //unique_ptr<int[]> up;     
  37.     unique_ptr<int[]> up(p5);    
  38.     for(int i = 0; i < 10; ++i)  
  39.         cout << up[i] << " ";  
  40.     cout << endl;  
  41.     //如果使用shared_ptr的話我們必須自己定義delete函數  
  42.     shared_ptr<int>sp(new int[10], [](int *p) { delete []p;});  
  43.     //智能指針不支持算數類型,如果要訪問數組中的元素,必須使用get函數返回一個內置指針。  
  44.     cout << (*sp.get()) << endl;  
  45.   
  46. }  



課後題12.24

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <string>  
  3.   
  4. using namespace std;  
  5.   
  6. int main()  
  7. {  
  8.     string s1;  
  9.     cin >> s1;  
  10.     string *p = new string(s1);  
  11.     const char *p1 = new char[s1.size()];  
  12.     p1= (*p).c_str();                   //轉換爲一個c風格的字符串,但是是const類型的  
  13.     cout << "*p1 " << *p1 << endl;      //只會輸出第一個字母,說明new創建返回的不是數組類型的指針,而是元素類型  
  14.     cout << "p1 ";   
  15.     for(int i = 0; i < s1.size(); ++i)  
  16.         cout << p1[i] << " ";  
  17.     cout << endl;  
  18.     const char *p2 = new char[10];  
  19.     string ss;  
  20.     ss = "aaaaaaaaaaaaaaaaaaaaaa";      //超出了動態分配的內存空間       
  21.     p2 = ss.c_str();                      
  22.     cout << "p2 ";   
  23.     for(int i = 0; i < ss.size(); ++i)  //結果還是正常輸出了!  
  24.         cout << p2[i] << " ";  
  25.     cout << endl;  
  26. }  



<2.使用allocator類

引入allocator的原因是new類上的缺陷

new它將內存分配和對象構造結合到了一起

比如:string *p = new string;

new是現在找一塊內存分配,不夠繼續malloc,在分配內存的地址上調用構造函數,delete也一樣,在釋放內存的時候也會調用析構函數。

內置類型要指定初值。

但是如果我們希望指定它的初值,不讓它調用默認構造函數new就不可行了,而且本身調用了一次構造函數,然後我們賦值了一次。

更重要的是,沒有默認構造函數的就不能動態分配內存了。

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <memory>  
  3. #include <string>  
  4.   
  5. using namespace std;  
  6.   
  7. int main()  
  8. {  
  9.     //allocator<T>a;  定義了一個名爲a的allocator對象,可以爲T對象分配空間  
  10.     allocator<string>alloc;  
  11.     //a.allocate(n);  爲T分配n個空間    
  12.     string *const p  = alloc.allocate(10);  //爲10個string分配了內存,且內存是原始的,未構造的  
  13.     cout << sizeof(p) << endl;  
  14.     //釋放p中地址的內存,這快內存保存了n個T對象,p必須是allocte返回的指針,n必須是allocate(n)的n  
  15.     //且在調用deallocate時必須先毀壞這塊內存中創建的對象  
  16.     alloc.deallocate(p,10);  
  17.     //p是allocate返回的指針,construction是傳遞給類型T的構造函數,在p指向的內存中構造一個對象  
  18.     //alloc.construct(p, construction)  
  19.     //對p指向的對象進行析構函數。  
  20.     //alloc.destroy(p);  
  21.       
  22.     allocator<string>alloc2;  
  23.     auto const p2 = alloc2.allocate(10);  
  24.     auto q = p2;  
  25.     auto q2 = p2;  
  26.     //爲了使用我們allocate的內存,必須用construct構造對象,使用未定義的內存,其行爲是未定義的。  
  27.     alloc2.construct(q++, "sssss");  
  28.     cout << *q2++ << endl;  
  29.     alloc2.construct(q++, "he");  
  30.     cout << *q2++ << endl;  
  31.     alloc2.construct(q++, 10, 'x');  
  32.     cout << *q2 << endl;  
  33.     //對使用過的內存進行釋放,調用string的析構函數,注意不能destory未使用的內存。  
  34.     while(q2 != p2)  
  35.         alloc2.destroy(q2--);  
  36.     //元素被銷燬後,我們可以重新使用這塊內存,也可以歸還給系統  
  37.     alloc2.deallocate(p2, 10);  
  38.     //deallocate的指針不能爲空,必須指向allocate分配的內存,且deallocate和allocate的大小相同。  
  39.   
  40. }  


標準庫還爲allocator定義了兩個伴隨算法

在未初始化的內存中創建對象,都定義在頭文件memory

[cpp] view plain copy
  1. uninitialized_copy(b,e,b2)      b,2是輸入容器的迭代器,b2是內存的起始地址,要保證空間足夠  
  2. uninitialized_copy_n(b,n,b2)    b是輸入容器的起始迭代器,複製n個,複製到以b2爲起始地址的動態內存中  
  3. uninitialized_fill(b,e,t)       b,e是動態內存的起始和終止位置,t是要fill的元素  
  4. uninitialized_fill_n(b,n,t)     b是動態內存的起始,fill n個,t是要fill的元素  

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <string>  
  3. #include <vector>  
  4. #include <memory>  
  5.   
  6. using namespace std;  
  7.   
  8. int main()  
  9. {  
  10.     //copy返回的是最後一個元素的下一個位置,fill返回void  
  11.     vector<string>ivec(10,"a");  
  12.     allocator<string>alloc;  
  13.     auto const p = alloc.allocate(ivec.size()*4);  
  14.     auto q = uninitialized_copy(ivec.begin(),ivec.end(), p);  
  15.     auto q2 = q;  
  16.     while(q-- != p)  
  17.         cout << *q << " ";  
  18.     cout << endl;  
  19.     uninitialized_fill_n(q2, ivec.size(), "b");  
  20.     for(auto i = 0; i < ivec.size(); ++i)  
  21.         cout << *q2++ << " ";  
  22.     cout << endl;  
  23.   
  24.     vector<string>ivec2(10,"c");  
  25.     auto q3 = uninitialized_copy_n(ivec2.begin(),10,q2);  
  26.     for(auto i = 0; i < ivec2.size(); ++i)  
  27.         cout << *q2++ << " ";  
  28.     cout << endl;  
  29.     uninitialized_fill(q3,q3+10, "d");  
  30.     for(auto i = 0; i < ivec2.size(); ++i)  
  31.         cout << *q3++ << " ";  
  32.     cout << endl;  
  33.   
  34. }  





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章