C++ 11 創建和使用共享 weak_ptr


1、為什么需要weak_ptr?

在正式介紹weak_ptr之前,我們先來回憶一下shared_ptr的一些知識。我們知道shared_ptr是采用引用計數的智能指針,多個shared_ptr實例可以指向同一個動態對象,並維護了一個共享的引用計數器。對於引用計數法實現的計數,總是避免不了循環引用(或環形引用)的問題,shared_ptr也不例外。

我們先來看看下面這個例子:

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

class ClassB;

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

class ClassB
{
public:
    ClassB() { cout << "ClassB Constructor..." << endl; }
    ~ClassB() { cout << "ClassB Destructor..." << endl; }
    shared_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;
    std::cout << "spa use_cout:" << spa.use_count() << " spb use_cout:" << spb.use_count() << std::endl;  //spa: 2 spb:2
  // 函數結束,思考一下:spa和spb會釋放資源么? 超過作用於時引用計數減一,此時為2,減一后不為0,所以內存不釋放
}

上面代碼的輸出如下:

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

從上面代碼中,ClassA和ClassB間存在着循環引用,從運行結果中我們可以看到:當main函數運行結束后,spa和spb管理的動態資源並沒有得到釋放,產生了內存泄露。為了解決類似這樣的問題,C++11引入了weak_ptr,來打破這種循環引用。

2、weak_ptr是什么?

weak_ptr是為了配合shared_ptr而引入的一種智能指針,它指向一個由shared_ptr管理的對象而不影響所指對象的生命周期,也就是將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數。不論是否有weak_ptr指向,一旦最后一個指向對象的shared_ptr被銷毀,對象就會被釋放。從這個角度看,weak_ptr更像是shared_ptr的一個助手而不是智能指針。

3、weak_ptr如何使用?

接下來,我們來看看weak_ptr的簡單用法。

3.1如何創建weak_ptr實例

當我們創建一個weak_ptr時,需要用一個shared_ptr實例來初始化weak_ptr,由於是弱共享,weak_ptr的創建並不會影響shared_ptr的引用計數值。

示例:

    {
        std::shared_ptr<int> fsPtr(new int(5));
        std::weak_ptr<int> fwPtr = fsPtr;

        //weak_ptr不會改變shared_ptr,但是會和shared_ptr的引用保持一致
        std::cout << "fsPtr use_count:" << fsPtr.use_count() << " fwPtr use_count:" << fwPtr.use_count() << std::endl;

        //fwPtr.lock()后會該變shared_ptr的引用計數(+1)
        //std::shared_ptr<int> fsPtr2 = fwPtr.lock();
        //std::cout << "fsPtr use_count:" << fsPtr.use_count() << " fwPtr use_count:" << fwPtr.use_count() << std::endl;

        //編譯報錯,weak_ptr沒有重載*,->操作符,因此不可直接通過weak_ptr使用對象,只能通過lock()使用shared_ptr來操作
        //std::cout << " number is " << *fwPtr << std::endl;

        fsPtr.reset();
        if (fwPtr.expired())
        {
            std::cout << "shared_ptr object has been destory" << std::endl;
        }

        std::shared_ptr<int> fsPtr3 = fwPtr.lock();                //fsPtr3為NULL
        std::cout << " number is " << *fsPtr3 << std::endl;     //運行時中斷
    }

3.2如何判斷weak_ptr指向對象是否存在

既然weak_ptr並不改變其所共享的shared_ptr實例的引用計數,那就可能存在weak_ptr指向的對象被釋放掉這種情況。這時,我們就不能使用weak_ptr直接訪問對象。那么我們如何判斷weak_ptr指向對象是否存在呢?C++中提供了lock函數來實現該功能。如果對象存在,lock()函數返回一個指向共享對象的shared_ptr,否則返回一個空shared_ptr。

示例:

class A
{
public:
    A() : a(3) { cout << "A Constructor..." << endl; }
    ~A() { cout << "A Destructor..." << endl; }

    int a;
};

int main() {
    shared_ptr<A> sp(new A());
    weak_ptr<A> wp(sp);
    //sp.reset();

    if (shared_ptr<A> pa = wp.lock())
    {
        cout << pa->a << endl;
    }
    else
    {
        cout << "wp指向對象為空" << endl;
    }
}

試試把sp.reset()這行的注釋去掉看看結果有什么不同。

除此之外,weak_ptr還提供了expired()函數來判斷所指對象是否已經被銷毀。

示例:

class A
{
public:
    A() : a(3) { cout << "A Constructor..." << endl; }
    ~A() { cout << "A Destructor..." << endl; }

    int a;
};

int main() {
    shared_ptr<A> sp(new A());
    weak_ptr<A> wp(sp);
    sp.reset(); // 此時sp被銷毀
    cout << wp.expired() << endl;  // true表示已被銷毀,否則為false
}

代碼輸入如下:

A Constructor...
A Destructor...

3.3如何使用weak_ptr

weak_ptr並沒有重載operator->和operator *操作符,因此不可直接通過weak_ptr使用對象,典型的用法是調用其lock函數來獲得shared_ptr示例,進而訪問原始對象。

最后,我們來看看如何使用weak_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會釋放資源么?因為沒改變shared_ptr的引用計數,此時引用計數為1,超過作用域后自動釋放
}

輸出結果如下:

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

從運行結果可以看到spa和spb指向的對象都得到釋放!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM