STL裏的四種智能指針 auto_ptr、scoped_ptr、shared_ptr、weak_ptr


基於安全考慮:

auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation; 
vocaticn = ps;

因爲程序將試圖刪除同一個對象兩次,要避免這種問題,方法有多種:

(1)定義賦值運算符,使之執行深複製。這樣兩個指針將指向不同的對象,其中的一個對象是另一個對象的副本,缺點是浪費空間,所以智能指針都未採用此方案。
(2)建立所有權概念。對於特定的對象,只能有一個智能指針可擁有,這樣只有擁有對象的智能指針的析構函數會刪除該對象。然後讓賦值操作轉讓所有權。這就是用於auto_ptr和unique_ptr 的策略,但unique_ptr的策略更嚴格。
(3)創建智能更高的指針,跟蹤引用特定對象的智能指針數。這稱爲引用計數。例如,賦值時,計數將加1,而指針過期時,計數將減1,。當減爲0時才調用delete。這是shared_ptr採用的策略。


1. unique_ptr:(防止析構一塊內存多次)

unique_ptr由C++11引入,旨在替代不安全的auto_ptr。
unique_ptr不共享它的所管理的對象。它無法複製到其他unique_ptr,無法通過值傳遞到函數,也無法用於需要副本的任何標準模板庫 (STL)算法。只能移動 unique_ptr,即對資源管理權限可以實現轉移。


//智能指針的創建  
unique_ptr<int> u_i; //創建空智能指針
u_i.reset(new int(3)); //"綁定”動態對象  
unique_ptr<int> u_i2(new int(4));//創建時指定動態對象
unique_ptr<T,D> u(d);   //創建空unique_ptr,執行類型爲T的對象,用類型爲D的對象d來替代默認的刪除器delete

//所有權的變化  
int *p_i = u_i2.release(); //釋放所有權  
unique_ptr<string> u_s(new string("abc"));  
unique_ptr<string> u_s2 = std::move(u_s); //所有權轉移(通過移動語義),u_s所有權轉移後,變成“空指針” 
u_s2.reset(u_s.release());//所有權轉移
u_s2=nullptr;//顯式銷燬所指對象,同時智能指針變爲空指針。與u_s2.reset()等價

2.auto_ptr:爲什麼不用它而用unique_ptr(廢柴版unique_ptr)

使用unique_ptr時編譯出錯,與auto_ptr一樣,unique_ptr也採用所有權模型,但在使用unique_ptr時,程序不會等到運行階段崩潰,而在編譯期因下述代碼行出現錯誤。一句話總結就是:避免因潛在的內存問題導致程序崩潰。

int main()
{
    auto_ptr<string> films[5] ={
    auto_ptr<string> (new string("Fowl Balls")),
    auto_ptr<string> (new string("Duck Walks")),
    auto_ptr<string> (new string("Chicken Runs")),
    auto_ptr<string> (new string("Turkey Errors"))
    };
    auto_ptr<string> pwin;
    pwin = films[2]; 
    // films[2] loses ownership. 將所有權從films[2]轉讓給pwin,此時films[2]不再引用該字符串從而變成空指針

    for(int i = 0; i < 4; ++i)
    {
        cout << *films[i] << endl;
    }
    return 0;
}

從上面可見,unique_ptr比auto_ptr更加安全,因爲auto_ptr有拷貝語義,拷貝後原象變得無效,再次訪問原對象時會導致程序崩潰;unique_ptr則禁止了拷貝語義,但提供了移動語義,即可以使用std::move()進行控制權限的轉移

unique_ptr<string> upt(new string("lvlv"));
unique_ptr<string> upt1(upt);   //編譯出錯,已禁止拷貝
unique_ptr<string> upt1=upt;    //編譯出錯,已禁止拷貝
unique_ptr<string> upt1=std::move(upt);  //控制權限轉移,正確的寫法

auto_ptr<string> apt(new string("lvlv"));
auto_ptr<string> apt1(apt); //編譯通過
auto_ptr<string> apt1=apt;  //編譯通過
  • 使用shared_ptr時運行正常,因爲shared_ptr採用引用計數,pwin和films[2]都指向同一塊內存,在釋放空間時因爲事先要判斷引用計數值的大小因此不會出現多次刪除一個對象的錯誤。

3. shared_ptr

參看內存垃圾管理(智能指針)

4. weak_ptr(shared_ptr的小跟班)

被設計爲與shared_ptr共同工作,可以從一個shared_ptr或者另一個weak_ptr對象構造而來。

爲什麼用weak_ptr就可以解決循環引用的問題?簡單點的來說:weak_ptr的構造和析構不會引起引用計數的增加或減少。weak_ptr必須與shared_ptr配合使用,不能單獨使用。

template<typename T>
struct ListNode{
    T _value;
    weak_ptr<ListNode> _prev;
    weak_ptr<ListNode> _next;
     
    ListNode(const T & value)
        :_value(value)
        ,_prev(NULL)
        ,_next(NULL){}
 
    ~ListNode(){
        std::cout<<"~ListNode()"<<std::endl;
    }
};
void TestWeekPtr(){
    std::shared_ptr<ListNode<int>> sp1(new ListNode<int>(10));
    std::shared_ptr<ListNode<int>> sp2(new ListNode<int>(20));
    sp1->_next = sp2;
    sp2->_prev = sp1;
 
    std::cout<<sp1.use_count()<<std::endl;
    std::cout<<sp2.use_count()<<std::endl;
}

5. scoped_ptr (防拷貝賦值智能指針)

循環引用:

一般來講,解除這種循環引用有下面三種可行的方法:
(1)當只剩下最後一個引用的時候需要手動打破循環引用釋放對象。
(2)當parent的生存期超過children的生存期的時候,children改爲使用一個普通指針指向parent。
(3)使用弱引用的智能指針打破這種循環引用。
雖然這三種方法都可行,但方法1和方法2都需要程序員手動控制,麻煩且容易出錯。這裏主要介紹一下第三種方法,使用弱引用的智能指針std:weak_ptr來打破循環引用。

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