C++相關:動態記憶體和智能指標

來源:互聯網
上載者:User

標籤:nullptr   帶來   額外   拷貝   throw   聲明   另一個   弱引用   size   

前言

在C++中,動態記憶體的管理是通過運算子newdelete來完成的。但使用動態記憶體很容易出現問題,因為確保在正確的時間釋放記憶體是及其困難的。有時候我們會忘記記憶體的的釋放,這種情況下就會產生記憶體泄露;有時候又會在尚有指標引用的情況下就用delete釋放了記憶體,這樣又會產生引用非法記憶體的指標(野指標)。因此,為了更容易地使用動態記憶體,C++標準庫提供了兩種智能指標,shared_ptrunique_ptr。shared_ptr允許多個指標指向同一個對象,unique_ptr則獨佔指向的對象。另外,還有一種叫weak_ptr的伴隨類,他是一種弱引用,指向shared_ptr所管理的對象。三者定義於memory標頭檔中。

shared_ptr類

聲明方式類似vector,屬於模板類,如下

shared_ptr<string> p1;     //聲明了一個指向string的智能指標,預設空

解引用等使用的方式類似普通指標

if( p1 && p1->empty())  *p1 = "hi!"; //如果p1指向一個空string,解引用並賦一個新值

兩種智能指標公用的操作

shared_ptr<T> sp;unique_ptr<T> up;//假設聲明了兩個名為p、q的智能指標p->mem;  //等價於(*p).memp.get();     //返回p中存放的指標//交換二者儲存的指標swap(p,q);p.swap(q);

shared_ptr專屬的操作

p.unique();  //是否專屬p.use_count; //返回p共用對象的智能指標數量p = q;  //該操作會遞減p的引用計數,遞增q的引用計數;若p的引用計數變為0,則將其管理的原記憶體釋放make_shared<T>(args);//該方法返回一個shared_ptr,指向一個T類型的對象,並使用args初始化該對象,具體見下文 

make_shared函數

最安全的分配和使用動態記憶體的方法。

shared_ptr<int> p3 = make_shared<int>(42);//指向值為42的int的智能指標。//或者也可以auto p3 = make_shared<int>(42);

每一個shared_ptr都有關聯的計數器,稱為引用計數。當用一個shared_ptr ——p去初始化另個一個q時,或者將p作為參數傳遞給函數,或者作為函數傳回值時,它關聯的對象的引用計數就會遞增;而如果給它賦一個新值或者是shared_ptr被銷毀時,之前關聯的計數器就減1,當一個shared_ptr的計數器為0時,他就會自動釋放管理的對象的記憶體。

動態分配的const對象

const int *pci = new int(1024);const string *pcs = new string;/*const修飾的對象必須初始化雖然對象的值不能被修改,但是本身可以銷毀的*/delete pcs;//這是可以的

PS:用delete釋放一個null 指標總是沒有錯誤的。

記憶體耗盡

如果記憶體耗盡的情況下,使用new會分配失敗並拋出std::alloc異常。

可以使用

int *p2 = new (nothrow) int;

的形式來向new傳遞額外的參數,從而告訴它不能拋出異常,這種稱為定位new。

 動態對象的生存周期直到被釋放為止

Foo* factory(T arg){   return new Foo(arg);}void use_factory(T arg){   Foo *p = factory(arg);}

上述的代碼,雖然p在離開範圍以後被銷毀了,但他所指向的動態記憶體並沒有被釋放,不注意的話很可能記憶體流失!

所以,正確的做法如下:

void use_factory(T arg){   Foo *p = factory(arg);   //這塊記憶體不再使用了就釋放   delete p;}

概括來說,由內建指標(不是智能指標)管理的動態記憶體在被顯式地釋放前會一直存在,直到手動釋放或者程式結束時才會被回收。因此,智能指標的使用能夠避免很多忘記釋放記憶體等失誤帶來的麻煩。

另外,delete之後,雖然指標已經無效,但是它依然儲存著釋放的記憶體的地址,因此為了避免誤操作最好將指標置空。

int *p(new int(42));auto q = p;delete p;p = nullptr;

但是這樣提供的保護還是有限的,如上述代碼雖然將p置空,但是q仍然指向那塊記憶體,仍然存在隱患。

 shared_ptr和new結合使用

//錯誤的方式,智能指標的建構函式由explicit修飾,不支援將內建指標隱式轉換為智能指標shared_ptr<int> p1= new int(1024);//正確方式shared_ptr<int> p2(new int(1024));p2.reset(); //若p2是唯一指向,則釋放其指向的記憶體共置空p2.reset(q) //令p2指向q,否則置空//同樣的,傳回值如果時內建指標也會報錯shared_ptr<int> clone(int p){    return new int(p);  //錯誤,無法隱式轉換為智能指標}

  

 智能指標和普通指標最好不要混合使用

void process(shared_ptr<int> ptr){}/*ptr離開範圍被銷毀-----------------如果使用普通指標*/int *x(new int(1024));process(x);//出錯,無法轉換process(shared_ptr<int>(x));//合法,但是x指向的記憶體在內部會被釋放掉!!int j = *x; //錯誤,未定義,x是一個空懸指標

上述代碼中,shared_ptr通過x拷貝構造了一個智能指標ptr傳遞進process,這個時候的引用計數為1,而離開範圍後ptr被銷毀,其指向的對象不再被引用,因此記憶體被回收,指標x因此無家可指變為野指標。

另外,也盡量避免使用get初始化另一個智能指標,也不要delete get()返回的內建指標。

使用自訂的釋放操作

struct destination;       //串連目的地struct connection;       //串連資訊connection connect(destination *);  //開啟串連void disconnect(connection);          //關閉指定串連void end_connection(connection *p){     disconnect(*p);}void f(destination &d /*其他參數*/){    connection c = connect(&d);    shared_ptr<connection> p(&c,end_connection);    //使用串連   //當f退出時(即使為異常退出),connection也會被正確關閉}

上述代碼類比的一個網路程式庫的代碼使用。

當p被銷毀時,她不會對儲存的的指標delete,而是調用end_connection,接下來end_connection會調用disconnect,從而確保串連被關閉。如果f正常退出,那麼p的銷毀會作為結束處理的一部分,如果發生了異常,p同樣被銷毀,串連從而被關閉。

unique_ptr類

顧名思義,獨一無二的指標,與shared_ptr不同,某個時刻只能由一個unique_ptr指向一個給定對象。聲明以及初始化如下

unique_ptr<int> p2(new int(42));

由於unique_ptr獨享其對象,所以它不支援普通的拷貝和賦值操作

unique_ptr<string> p1(new string("Stegosaurus"));unique_ptr<string> p2(p1);  //錯誤:不支援拷貝unique_ptr<string> p3‘p3 = p2;                             //錯誤,不支援賦值

unique_ptr的相關操作

unique_ptr<T> u1; //空unique_ptr指標unique_ptr<T,D> u2; //使用D對象來代替delete釋放unique_ptr<T,D> u(new class());u = nullptr; //釋放u指向的對象共置空u.release(); //u會放棄對該對象的控制權(記憶體不會釋放),返回一個指向對象的指標,共置空自己u.reset();   //釋放u所指對象u,reset(q);//如果提供了內建指標q,則指向q所指對象;否則u置空

當unique_ptr將要被銷毀時,可以“特殊地”被拷貝或者賦值,比如下面這種情況

unique_ptr<int> clone(int p){    return unique_ptr<int>(new int(p));  //正確}//或者unique_ptr<int> clone(int p){   unique_ptr<int> ret(new int(p));   //......   return ret; //正確}

weak_ptr類

weak_ptr是一種不控制所指向對像生命週期的智能指標,它指向由一個shared_ptr管理的對象。將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數,當最後一個指向對象的shared_ptr被銷毀時,對象會被釋放(即使有weak_ptr指向)。

weak_ptr的操作

weak_ptr<T>w;weak_ptr<T>w(sp);//使用一個shared_ptr初始化w = p;               //p可以是一個sp也可是一個wp。賦值後w,p共用對象w.reset();//置空w.use_count();       //同w共用對象的shared_ptr的數量w.expired();        //w.use_count()為0返回true,否則返回falsew.lock();            //expired為true,返回一個空的shared_ptr;否則返回一個指向w的對象的shared_ptr

allocator類

定義在memory中,它提供了一種類型感知的記憶體配置方式,將記憶體配置和物件建構分離開來。它分配的記憶體是原始的、未構造的。基本用法如下:

allocator<string> alloc;  //分配string的allocator對象auto const p = alloc,allocate(n); //分配n個未初始化的string

allocator的操作

allocator<T> a;a.allocate(n);a.deallocate(p,n); /*釋放從T*指標p中地址開始的記憶體,這塊記憶體儲存了n個類型為T的對象;p必須是一個先前由allocate返回的指標,且n必須是p建立時所要求的大小。在調用deallocate之後,使用者必須對每個在這塊記憶體中建立的對象調用destroy*/a.construct(p,args);/*p必須是一個類型為T*的指標,指向一塊原始記憶體;args被傳遞給類型為T的建構函式,用來在p指向的記憶體中構造一個對象*/a.destroy(p); //p為T*類型的指標,此演算法對p所指向的對象執行解構函式

allocator分配的記憶體是未構造的,所以我們必須用construct構造對象,並且只能對構造了的對象執行destroy操作。

銷毀的參考代碼如下:

while(q != p)   alloc.destroy(--q);

 一旦所有元素被銷毀後,就可以重新使用這部分記憶體來儲存其他的string,也可以使用 alloc.deallocate(p,n)來釋放記憶體。

參考資料

《C++ Primer 第5版》 電子工業出版社    【美】  Stanley B. Lippman  && Josee Lajoie && Barbara E.Moo

C++相關:動態記憶體和智能指標

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.