C++11學習之share_ptr和weak_ptr

一、shared_ptr學習

1.shared_ptr和weak_ptr 基礎概念

  • shared_ptr與weak_ptr智能指針均是C++ RAII的一種應用,可用於動態資源管理
  • shared_ptr基於“引用計數”模型實現,多個shared_ptr可指向同一個動態對象,並維護了一個共享的引用計數器,記錄了引用同一對象的shared_ptr實例的數量。當最後一個指向動態對象的shared_ptr銷燬時,會自動銷燬其所指對象(通過delete操作符)。
  • shared_ptr的默認能力是管理動態內存,但支持自定義的Deleter以實現個性化的資源釋放動作。
  • weak_ptr用於解決“引用計數”模型循環依賴問題,weak_ptr指向一個對象,並不增減該對象的引用計數器

2.shared_ptr的基本操作

#include <memory>
#include <iostream>

struct Foo {
    Foo() { std::cout << "Foo...\n"; }
    ~Foo() { std::cout << "~Foo...\n"; }
};

struct D { 
    //刪除p所指向的Foo對象
    void operator()(Foo* p) const {
        std::cout << "Call delete for Foo object...\n";
        delete p;
    }
};

int main()
{   
    // constructor with no managed object
    std::shared_ptr<Foo> sh1;

    // constructor with object
    std::shared_ptr<Foo> sh2(new Foo);
    std::shared_ptr<Foo> sh3(sh2);
    std::cout << sh2.use_count() << '\n';
    std::cout << sh3.use_count() << '\n';

    //constructor with object and deleter
    std::shared_ptr<Foo> sh4(new Foo, D());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

構造方法: 
1.通過make_shared函數構造 
auto s_s = make_shared(“hello”);

2.通過原生指針構造 
int* pNode = new int(5); 
shared_ptr s_int(pNode); 
//獲取原生指針 
int* pOrg = s_int.get();

3.通過賦值函數構造shared_ptr

4.重載的operator->, operator *,以及其他輔助操作如unique()、use_count(), get()等成員方法。

3 實驗智能指針引用計數,增加和減少的規律

實驗的主要內容有: 
1.shared_ptr變量在生命週期中銷燬後,引用計數是否減1? 
2.shared_ptr作爲函數參數,分爲傳值和傳引用,引用計數如何變化? 
2.函數返回值爲shared_ptr類型時,引用計數是否會變化?

帶着這幾個問題,我們來看下代碼.

#include <iostream>
#include <memory>
using namespace std;

void Func1(shared_ptr<int> a)
{
    cout<<"Enter Func1"<<endl;
    cout<<"Ref count: "<<a.use_count()<<endl;
    cout<<"Leave Func1"<<endl;
}

shared_ptr<int> Func2(shared_ptr<int>& a)
{
    cout<<"Enter Func2"<<endl;
    cout<<"Ref count: "<<a.use_count()<<endl;
    cout<<"Leave Func2"<<endl;
    return a;
}

int main()
{
    //構造一個指向int類型對象的指針aObj1,引用計數+1
    shared_ptr<int> aObj1(new int(10));
    cout<<"Ref count: "<<aObj1.use_count()<<endl;

    {
        //同aObj1,不過由於生存週期在括號內,所以aObj2會被銷燬
        shared_ptr<int> aObj2 = aObj1;
        cout<<"Ref count: "<<aObj2.use_count()<<endl;//引用計數-1
    }

    //在調用函數時,參數爲shared_ptr類型,參數爲傳值類型,智能指針引用計數+1
    Func1(aObj1);

    //在調用函數時,參數爲shared_ptr類型,參數爲傳引用類型,智能指針引用計數不變
    Func2(aObj1);

    shared_ptr<int> aObj3 = Func2(aObj1);//引用計數+1
    cout<<"Ref count:"<<aObj3.use_count()<<endl;

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

運行結果如下: 
這裏寫圖片描述

有效的掌握好智能指針的引用計數的變化規律,才能把程序寫的更好.

4. shared_ptr的應用場景以及使用注意事項

4.1 對象之間“共享數據”,對象創建與銷燬“分離” 
4.2 放入容器中的動態對象,使用shared_ptr包裝,比unique_ptr更合適 
4.3 管理“動態數組”時,需要制定Deleter以使用delete[]操作符銷燬內存,因爲shared_ptr並沒有針對數組的特化版本(unique_ptr有針對數組的特化版本)

5.shared_ptr的線程安全問題

  1. 同一個shared_ptr被多個線程讀,是線程安全的;
  2. 同一個shared_ptr被多個線程寫,不是 線程安全的;
  3. 共享引用計數的不同的shared_ptr被多個線程寫,是線程安全的。 
    對於第三點,我們一般採用: 
    對於線程中傳入的外部shared_ptr對象,在線程內部進行一次新的構造,例如: sharedptr AObjTmp = outerSharedptrObj;

二、weak_ptr學習

我們先搞清楚,weak_ptr爲什麼出現,或者說它是爲了解決什麼問題而存在的(存在即合理),哈哈

class Parent
{
public:
    shared_ptr<Child> child;
};

class Child
{
public:
    shared_ptr<Parent> parent;
};

shared_ptr<Parent> pA(new Parent);
shared_ptr<Child> pB(new Child);
pA->child = pB;
pB->parent = pA;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在Parent類中存儲了指向Child類對象的智能指針成員變量,而在Child類中也存儲了指向Parent類對象的智能指針成員變量,如此就會造成環形引用,這個成因在C++中很好解釋.

要解決環形引用的問題,沒有特別好的辦法,一般都是在可能出現環形引用的地方使用weak_ptr來代替shared_ptr。說到了weak_ptr,那下面就接着總結weak_ptr吧。

下面我們來一起學習下weak_ptr這個東東

weak_ptr指向shared_ptr指針指向的對象的內存,卻並不擁有該內存。 
但是,使用weak_ptr成員lock,則可返回其指向內存的一個shared_ptr對象,且在所指對象內存已經無效時,返回指針空值(nullptr)。由於weak_ptr是指向shared_ptr所指向的內存的,所以,weak_ptr並不能獨立存在。

#include <iostream>
#include <memory>
using namespace std;

void Check(weak_ptr<int> &wp)
{
    shared_ptr<int> sp = wp.lock(); // 重新獲得shared_ptr對象
    if (sp != nullptr)
    {
        cout << "The value is " << *sp << endl;
    }
    else
    {
        cout << "Pointer is invalid." << endl;
    }
}

int main()
{
    shared_ptr<int> sp1(new int(10));
    shared_ptr<int> sp2 = sp1;
    weak_ptr<int> wp = sp1; // 指向sp1所指向的內存

    cout << *sp1 << endl;
    cout << *sp2 << endl;
    Check(wp);

    sp1.reset();
    cout << *sp2 << endl;
    Check(wp);

    sp2.reset();
    Check(wp);

    system("pause");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

學習編程最好的方式就是一步步的跟蹤去調試. 
借鑑上面的代碼,我們在使用weak_ptr時也要當心,時刻需要判斷weak_ptr對應的shared_ptr是否爲空,weak_ptr並不會增加shared_ptr的引用計數.

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