1. 程式人生 > >C++ 11 智慧指標淺析

C++ 11 智慧指標淺析

定義

為了實現指標自動回收的物件,表現和指標一樣,實際上它利用了棧的機制,每一個智慧指標都是一個模板類,呼叫智慧指標實際上是建立了一個智慧指標的物件,物件生命週期到達盡頭的時候,會自動呼叫智慧指標的解構函式,在解構函式裡,釋放掉它管理的記憶體,從而避免手動delete。

Java裡有類似實現(jdk1.2以後有strong,soft,weak,phantom引用)

C++11之前有auto_ptr,但是與STL不相容,C++11時被棄用

shared_ptr是引用計數的智慧指標,而unique_ptr不是。這意味著,可以有多個shared_ptr例項指向同一塊動態分配的記憶體,當最後一個shared_ptr離開作用域時,才會釋放這塊記憶體。shared_ptr也是執行緒安全的。

另一方面,unique_ptr意味著所有權。單個unique_ptr離開作用域時,會立即釋放底層記憶體。

預設的智慧指標應該是unique_ptr。只有需要共享資源時,才使用shared_ptr,weak_ptr是shared_ptr的弱引用,不增加其引用計數器。

指標 簡要描述
shared_ptr 允許多個指標指向同一個物件
unique_ptr 獨佔所指向的物件
weak_ptr shared_ptr的弱引用

share_ptr

允許多個shared_ptr指向同一個物件,使用引用計數器來判斷是否需要釋放。訪問時像C++一般的指標一樣。

最安全和高效的方法是呼叫make_shared庫函式,該函式會在堆中分配一個物件並初始化,最後返回指向此物件的share_ptr例項。如果你不想使用make_ptr,也可以先明確new出一個物件,然後把其原始指標傳遞給share_ptr的建構函式。

可以*pInt也可以pInt->。指標的拷貝p=q會使q的引用計數++,p的–,如下:

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

class Example
{
public:
    Example() : e(1) { cout
<< "Example Constructor..." << endl; } ~Example() { cout << "Example Destructor..." << endl; } int e; }; int main() { shared_ptr<Example> pInt(new Example()); shared_ptr<Example> pInt3=make_shard<Example>(); auto pInt4=pInt3; cout << (*pInt).e << endl; cout << "pInt引用計數: " << pInt.use_count() << endl; shared_ptr<Example> pInt2 = pInt; cout << "pInt引用計數: " << pInt.use_count() << endl; cout << "pInt2引用計數: " << pInt2.use_count() << endl; cout << "pInt3引用計數: " << pInt3.use_count() << endl; cout << "pInt4引用計數: " << pInt4.use_count() << endl; pInt3=pInt; cout << "pInt引用計數: " << pInt.use_count() << endl; cout << "pInt2引用計數: " << pInt2.use_count() << endl; cout << "pInt3引用計數: " << pInt3.use_count() << endl; cout << "pInt4引用計數: " << pInt4.use_count() << endl; } Example Constructor... Example Constructor... 1 pInt引用計數: 1 pInt引用計數: 2 pInt2引用計數: 2 pInt3引用計數: 2 pInt4引用計數: 2 pInt引用計數: 3 pInt2引用計數: 3 pInt3引用計數: 3 pInt4引用計數: 1 Example Destructor... Example Destructor...

shared_ptr提供了兩個函式來檢查其共享的引用計數值,分別是unique()和use_count()。

在前面,我們已經多次使用過use_count()函式,該函式返回當前指標的引用計數值。值得注意的是use_count()函式可能效率很低,應該只把它用於測試或除錯。

unique()函式用來測試該shared_ptr是否是原始指標唯一擁有者,也就是use_count()的返回值為1時返回true,否則返回false。

unique_ptr

與shared_ptr不同,某一時刻,只能有一個unique_ptr指向一個給定的物件。因此,當unique_ptr被銷燬,它所指的物件也會被銷燬。unique_ptr沒有類似make_shared函式只能通過unique_ptr<int> pInt(new int(5)); 無法複製構造和賦值。但是可以從函式中返回。也可以同過指標操作。類似auto_ptr

可以進行移動構造和移動賦值操作,通過std::move()函式。

int main() {
    unique_ptr<int> pInt(new int(5));
    unique_ptr<int> pInt2 = std::move(pInt);    // 轉移所有權
    //cout << *pInt << endl; // 出錯,pInt為空
    cout << *pInt2 << endl;
    unique_ptr<int> pInt3(std::move(pInt2));
}
  • 常見場景:

    • 為動態申請的資源提供異常安全保證
    void Func()
    {
        unique_ptr<int> p(new int(5));
    
        // ...(可能會丟擲異常)
    }
    • 返回函式內動態申請資源的所有權
    unique_ptr<int> Func(int p)
    {
      unique_ptr<int> pInt(new int(p));
      return pInt;    // 返回unique_ptr
    }
    
    int main() {
      int p = 5;
      unique_ptr<int> ret = Func(p);
      cout << *ret << endl;
      // 函式結束後,自動釋放資源
    }
    • 在容器中儲存指標
    int main() {
      vector<unique_ptr<int>> vec;
      unique_ptr<int> p(new int(5));
      vec.push_back(std::move(p));    // 使用移動語義
    }
    • 管理動態陣列
    int main() {
      unique_ptr<int[]> p(new int[5] {1, 2, 3, 4, 5});
      p[0] = 0;   // 過載了operator[]
    }
    • 作為auto_ptr的代替品

weak_ptr

使用計數器的shared_ptr很容易就產生迴圈引用的問題。這時如果用一個weak_ptr來指向shared_ptr不會增加他的引用計數器。可以使用lock()函式來安全地獲取一個weak_ptr的一個shared_ptr的引用,如果此時shared_ptr已被reset,則返回一個空的shared_ptr,賦值語句不成功。也可以安全地通過expired()函式來判斷是否過期。

使用時不能通過shared_ptr傳統指標操作,但是可以通過lock()來獲取他的share_ptr()。可用來解決迴圈引用的問題。

class ClassB;

class ClassA
{
public:
    ClassA() { cout << "ClassA Constructor..." << endl; }
    ~ClassA() { cout << "ClassA Destructor..." << endl; }
    weak_ptr<ClassB> pb;  // 在A中引用B
};

class ClassB
{
public:
    ClassB() { cout << "ClassB Constructor..." << endl; }
    ~ClassB() { cout << "ClassB Destructor..." << endl; }
    weak_ptr<ClassA> pa;  // 在B中引用A
};

int main() {
    shared_ptr<ClassA> spa = make_shared<ClassA>();
    shared_ptr<ClassB> spb = make_shared<ClassB>();
    spa->pb = spb;
    spb->pa = spa;
    // 函式結束,思考一下:spa和spb會釋放資源麼?
}

ClassA Constructor...
ClassB Constructor...
ClassA Destructor...
ClassB Destructor...
Program ended with exit code: 0