1. 程式人生 > >必須要注意的 C++ 動態記憶體資源管理(三)——智慧指標

必須要注意的 C++ 動態記憶體資源管理(三)——智慧指標

七.前言

        在前面一節,我們簡單實現了三種類型資源的”指標物件”。其實在c++11的標準庫中已經為我們準備了這樣的指標物件——智慧指標,分別是:shared_ptr , unique_ptr(取代了auto_ptr) , weak_ptr。下面我們簡單來介紹一下這三類智慧指標的特點和適用情況。

八.shared_ptr智慧指標

        shared_ptr指標適用的就是前一節所說的第3類資源指標。
        shared_ptr是模板類,所以可以通過任意的指標型別(這裡介紹使用的是動態分配的指標,但可以不是動態分配建立的,後面會介紹)來初始化。
        shared_ptr物件可以進行拷貝,賦值;當一個物件A拷貝B或者B賦值給A,其根本都是將B的資源共享給A,然後將該資源的引用次數增加;當引用該資源的最後一個shared_ptr物件銷燬的時候,會自動把該資源釋放掉。下面看shared_ptr的相關操作:
函式 介紹
shared_ptr<T>sp(T* res) 將資源res交給sp來管理,資源不使用時會自動釋放。
shared_ptr<T>sp = make_spared(params) 用params引數構造T型別的物件並且交給sp來管理。
p p可以作為條件判斷,若p指向一個物件,則為true,否則為false。
*p 對shared_ptr<T>物件解引用,得到的是由p管理的T型別物件的引用。
p->mem 等價於(*p).mem
p.get() 返回p中儲存的T*指標
swap(p,q) 交換p,q各自內部的T*指標,注意p,q型別要相同
p.swap(q) 同上
p = q p,q必須都是shared_ptr指標,並且各自管理的指標型別能相互轉換。此操作會遞減p的引用次數,遞增q的引用次數;若p的引用次數變為0,則其管理的原記憶體會自動釋放。
shared_ptr<T>sp(q) 使得sp引用q所佔資源。
p.reset() , p.reset(q) 放棄p對資源的引用,若為唯一引用,還會釋放記憶體。若傳有引數q,則讓p引用q所佔資源。
p.use_count() 返回與p共享的智慧指標數量;可能很慢,主要用於除錯。
p.unique() 若p.use_count()為1,返回true,否則返回false
我們用下面這段簡單的程式碼來理解shared_ptr智慧指標的使用:
int main()
{
    {
        //建立一個動態string初始化為"hello wold"並交付給p來管理。
        shared_ptr<string> p(new string("hello world")); 
        {
            shared_ptr<string> q(p);//用p初始化q,則該字串增添一個使用者
            cout << p.use_count() << endl;  // >> 2
            cout << *q << endl;   // >> "hello world"
            *q = *q + "!";  //解引用修改q管理的string物件。
        }// 跳出該程式碼塊,q生命週期結束,則該字串使用者又只剩p一個了。
        cout << p.use_count() << endl;  // >> 1
        cout << *p << endl; >> "hello world!"
    } // 跳出該程式碼塊,p生命週期結束,則該字串記憶體被釋放。
    return 0;  
}
事實上,在不是那種臨界資源的情況下,我們管理記憶體資源大多是使用shared_ptr智慧指標。而引用計數實現的有使用者保留,無使用者才釋放記憶體似乎完美地解決了記憶體釋放問題。然而shared_ptr也有缺陷,不過我們留到後面來講。

九.unique_ptr智慧指標

        unique_ptr指標適用的就是前一節所說的第2類資源指標。
        unique_ptr是模板類,所以可以通過任意的指標型別(這裡介紹使用的是動態分配的指標,但可以不是動態分配建立的,後面會介紹)來初始化。
        unique_ptr物件不可以進行拷貝,賦值;只能通過相關函式讓unique_ptr讓出的支配權然後其他unique_ptr接收。這樣就保證了支配權的唯一性。下面看unique_ptr的相關操作:
函式 介紹
unique_ptr<T>up(T* res) 將資源res交給up來管理,資源不使用時會自動釋放。
p p可以作為條件判斷,若p指向一個物件,則為true,否則為false。
*p 對shared_ptr<T>物件解引用,得到的是由p管理的T型別物件的引用。
p->mem 等價於(*p).mem。
p.get() 返回p中儲存的T*指標。
swap(p,q) 交換p,q各自內部的T*指標,注意p,q型別要相同。
p.swap(q) 同上。
p = q 這是無效操作,unique_ptr不能進行賦值。
unique_ptr<T>sp(q) 這是無效操作,unique_ptr不能進行拷貝。
p = nullptr 釋放p指向的物件,並將p置為控。
p.release() p放棄對指標的控制權,返回資源指標,並將p置為空。
p.reset(T* q) 釋放p指向的物件,並將p置為空,若傳入引數q,則讓p指向q該指標。
p.reset(q.release()) 釋放掉p指向的物件,q將自身指向物件的控制權轉移給p,然後q自身置為空。
我們用下面這段簡單的程式碼來理解unique_ptr智慧指標的使用:
int main()
{
    {
        //建立一個unique_ptr智慧指標q但是目前沒有管理任何記憶體。
        unique_ptr<string> q;
        {
            //建立一個動態string初始化為"hello wold"並交付給p來管理。
            unique_ptr<string> p(new string("hello world"));
            cout << *p << endl;   // >> "hello world"
            q.reset(p.release()); //p將支配權

        }// 跳出該程式碼塊,p生命週期結束,則該字串使用者又只剩p一個了。
        cout << *q << endl; //>> "hello world"
    } // 跳出該程式碼塊,q生命週期結束,則該字串記憶體被釋放。
    return 0;
}
        特別的,unique_ptr智慧指標還可以用於管理動態陣列:
int main()
{
    //當up釋放資源的時候會自動呼叫 delete[] 銷燬其指標。
    unique_ptr<int[]> up(new int[10]);

    //指向陣列的unique_ptr指標過載了[]
    for(int i = 0 ; i < 10 ; i ++) up[i] = i;

    for(int i = 0 ; i < 10 ; i ++) 
        printf("%d",up[i]);       // >> 012345679
    return 0;
}
        一般來說,我們不常用unique_ptr智慧指標來管理動態記憶體。而是管理類似於資料庫連線物件等資源。
        有時我們的資源記憶體不一定是通過動態分配,而且釋放有時候也不僅僅是delete 指標而已。所以,我們以後在使用unique_ptr指標的時候還需要傳入刪除器。這個,我們後面會詳細講到。

十.weak_ptr智慧指標

        weak_ptr 是一種不控制所指向物件生命期的智慧指標,它指向一個由 shared_ptr 管理的物件。將一個 weak_ptr 繫結到一個 shared_ptr 不會改變 shared_ptr 的引用次數。一旦最後一個指向物件的shared_ptr 被銷燬,物件就會被釋放;即使這個時候存在 weak_ptr 指向了該物件,物件還是會被釋放。所以我們可以把 weak_ptr 看作是一種”弱引用”。
函式 介紹
weak_ptr<T>wp 空 weak_ptr 可以指向型別為T的物件
weak_ptr<T>wp(sp) 與shared_ptr sp指向相同的物件,T必須可以轉化為sp指向物件的型別。
w = p p 可以是weak_ptr或者shared_ptr。賦值後w,p共享物件。但是w不干涉物件宣告週期。
w.reset() m置為空。
w.use_count() 與m共享資源的shared_ptr個數。
w.expired() 若w.use_count()為0,返回true,否則返回false。
w.lock() 如果expired為true,返回一個空的shared_ptr;否則返回指向w物件的shared_ptr。
        正是因為weak_ptr是一種”弱引用”,所以,很有可能在使用過程中某個weak_ptr被支配的shared_ptr給銷燬了;所以我們在使用weak_ptr之前需要呼叫w.lock()來判斷weak_ptr所指向的記憶體是否被釋放了。
基本上,智慧指標的基本操作就講到這裡,下一節會介紹智慧指標的刪除器以及相關注意事項。