1. 程式人生 > >深入學習c++--智能指針(二) weak_ptr(打破shared_ptr循環引用)

深入學習c++--智能指針(二) weak_ptr(打破shared_ptr循環引用)

aud nbsp else 之間 沒有 kobject 智能 one 訪問

1. 幾種智能指針

1. auto_ptr: c++11中推薦不使用他(放棄)

2. shared_ptr: 每添加一次引用 就+1,減少一次引用,就-1;做到指針進行共享

3. unique_ptr: 一個指針同時只能有一個使用者使用

4. weaked_ptr: 與shared_ptr搭配使用

1.1 weak_ptr

參考:https://zh.cppreference.com/w/cpp/memory/weak_ptr

  • std::weak_ptr 是一種智能指針,它對被 std::shared_ptr 管理的對象存在 非擁有性(“弱”)引用。在訪問所引用的對象前必須 先轉換

    std::shared_ptr。

  • std::weak_ptr 用來 表達臨時所有權的概念

    • 當某個對象只有存在時才需要被訪問,而且隨時可能被他人刪除時,可以使用 std::weak_ptr 來跟蹤該對象。

    • 需要獲得臨時所有權時,則將其轉換為 std::shared_ptr,此時如果原來的 std::shared_ptr被銷毀,則該對象的生命期將被延長至這個臨時的 std::shared_ptr 同樣被銷毀為止。

  • std::weak_ptr 的另一用法是:打斷 std::shared_ptr 所管理的對象組成的環狀引用。(打破shared_ptr的循環引用)


    • 若這種環被孤立(例如無指向環中的外部共享指針),則 shared_ptr 引用計數無法抵達零,而內存被泄露。

    • 能令環中的指針之一為弱指針以避免此情況。

循環引用的問題:該被調用的析構函數沒有被調用

#include <iostream>
#include <memory>
using namespace std;
 
class Parent;
typedef std::shared_ptr
<Parent> ParentPtr; class Child { public: ParentPtr father; Child() { cout << "hello Child" << endl; } ~Child() { cout << "bye Child\n"; } }; typedef std::shared_ptr<Child> ChildPtr; class Parent { public: ChildPtr son; Parent() { cout << "hello parent\n"; } ~Parent() { cout << "bye Parent\n"; } }; void testParentAndChild() { ParentPtr p(new Parent()); ChildPtr c(new Child()); p->son = c; c->father = p; } int main() { testParentAndChild(); return 0; }

技術分享圖片

問題:c只有調用p的析構的時候,才能被釋放。p只有調用c的析構的時候,才能被釋放。。形成了循環引用,造成了內存泄露

因此,引入新的智能指針,weak_ptr

1.2 weak_ptr基本用法

一旦外部指向shared_ptr資源失效,那麽weak_ptr管理的資源自動失效

#include <iostream>
#include <cassert>
#include <memory>
using namespace std;
 
class Object
{
public:
    Object(int id) : m_id(id) {
        std::cout << "init obj " << m_id << std::endl;
    }    
    ~Object() {
        std::cout << "bye bye " << m_id << std::endl;
    }
    int id() const {
        return m_id;
    }
private:
    int m_id;
};


void sharedPtrWithWeakPtr()
{
    typedef std::shared_ptr<Object> ObjectPtr;
    typedef weak_ptr<Object> WeakObjectPtr;
    
    ObjectPtr obj(new Object(1));
    
    // 一旦外部指向shared_ptr資源失效,那麽weak_ptr管理的資源自動失效 
    
    WeakObjectPtr weakObj2;           // 裸指針 
    WeakObjectPtr weakObj(obj);       // 指向shared_ptr指針 
    WeakObjectPtr weakObj3(obj);   
    cout << "obj use count is " << obj.use_count() << endl;   // 1--盡管weak_ptr也指向obj,但他只是監聽者,本身並不影響引用計數次數 
    {
        // weak_ptr使用方法 
        // 外部至少還有一個shared_ptr來管理資源,同時p自己本身的資源 -- p.use_count >= 2 
        auto p = weakObj.lock();         // auto == ObjectPtr
        if (p) {
            cout << p.unique() << endl;  // 0 -- p.use_count() >= 2
        }
        else {
        }
    }
    // 不指向某份資源 
//    obj.reset();
//    {
//        auto p = weakObj.lock();      // auto == ObjectPtr
//        if (p) {
//            assert(false);
//        } 
//        else {
//            cout << "null" << endl;   // 如果調用reset,就會執行這句話 
//        }
//    } 
    
    // 此時, Object(1)已經失效 
    obj.reset(new Object(2)) ;
    {
        auto p = weakObj.lock();       // 返回值如果有效, 在外部必須有weakObj指向同一個資源 
        if (p) {
            assert(false);
        }
        else {
            cout << "null " << endl;   // null
        }
    }
    
    weakObj = obj;    // 又有效了 
    // 想知道weak_ptr有沒有管理一份資源(外部有沒有資源), 又不想生成一個shared_ptr
    if (weakObj.expired()) {
        // 資源過期了 -- true 
        cout << "no ptr" << endl;
    }
    else {
        cout << "have resource\n";
    }
}

int main()
{
    
    sharedPtrWithWeakPtr();
    return 0;

}

技術分享圖片

1.3 解決類之間循環引用

一旦外部指向shared_ptr資源失效,那麽weak_ptr管理的資源自動失效

#include <iostream>
#include <cassert>
#include <memory>
using namespace std;
 
class Parent;
typedef std::shared_ptr<Parent> ParentPtr;
typedef std::weak_ptr<Parent> WeakParentPtr;

class Child
{
public:
    WeakParentPtr father;                 // 只有一環換成 weak_ptr, 即可打破環 
    Child() {
        cout << "hello Child" << endl;
    }
    ~Child() {
        cout << "bye Child\n";
    }
};
 
typedef std::shared_ptr<Child> ChildPtr;
typedef std::weak_ptr<Child> WeakChildPtr;

class Parent {
public:
    ChildPtr son;                 
    Parent() {
        cout << "hello parent\n";
    }
    ~Parent() {
        cout << "bye Parent\n";
    }
};


void testParentAndChild()
{
    ParentPtr p(new Parent());
    ChildPtr c(new Child());
    p->son = c;            
    c->father = p;        
    cout << (c->father).use_count() << endl;
    cout << (p->son).use_count() << endl;
}

int main()
{
    testParentAndChild();
    return 0;
}

技術分享圖片

析構函數成功調用, 註意析構順序,誰是weak_ptr對象,就先析構誰。

1.4 用 enable_shared_from_this從this轉換到shared_ptr

  • 類中函數接口需要一個本對象智能指針的const引用 (如何生成本對象的智能指針)

  • 用 enable_shared_from_this從this轉換到shared_ptr (使用CRTP來使用 本對象的智能指針)

主要代碼:

handleChildAndParent(shared_from_this(), ps);

完整代碼:

#include <iostream>
#include <cassert>
#include <memory>
using namespace std;
 
class Parent;
typedef std::shared_ptr<Parent> ParentPtr;
typedef std::weak_ptr<Parent> WeakParentPtr;
 
class Child : public std::enable_shared_from_this<Child>  // 奇異模板參數 CRTP
{
public:
    WeakParentPtr father;                 // 只有一環換成 weak_ptr, 即可打破環 
    Child() {
        cout << "hello Child" << endl;
    }
    ~Child() {
        cout << "bye Child\n";
    }
    void CheckRelation() {
        
    }
};
 
typedef std::shared_ptr<Child> ChildPtr;
typedef std::weak_ptr<Child> WeakChildPtr;

void handleChildAndParent(const ParentPtr& p, const ChildPtr& c);

class Parent : public enable_shared_from_this<Parent> {
public:
    WeakChildPtr son;                 
    Parent() {
        cout << "hello parent\n";
    }
    ~Parent() {
        cout << "bye Parent\n";
    }
    void CheckRelation() {
        auto ps = son.lock();
        if (ps) {
            //原理: 類中存在weak_ptr指針,通過一系列方式轉換成 shared_ptr,然後傳參 
            handleChildAndParent(shared_from_this(), ps);    // 使用CRTP來使用 本對象的指針 ★ ★ ★ ★ 
        }
        cout << "after call checkRelation\n";
    }
};

void testParentAndChild()
{
    ParentPtr p(new Parent());
    ChildPtr c(new Child());
    p->son = c;            
    c->father = p;        
    cout << (c->father).use_count() << endl;
    cout << (p->son).use_count() << endl;
    
    p->CheckRelation();
}

void handleChildAndParentRef(const Parent& p, const Child& c)
{
    auto cp = c.father.lock();
    auto pc = p.son.lock();
    if (cp.get() == &p && pc.get() == &c) 
    {
        cout << "right relation" << endl;
    }
    else {
        cout << "oop !!!\n";
    }
}

// const xxx&: 減少拷貝次數 
void handleChildAndParent(const ParentPtr& p, const ChildPtr& c)
{
    auto cp = c->father.lock();
    auto pc = p->son.lock();
    if (cp == p && pc == c)
    {
        cout << "right relation\n";
    }
    else {
        cout << "oop!!!\n";
    }
}

int main()
{
    testParentAndChild();
    return 0;
}

技術分享圖片

深入學習c++--智能指針(二) weak_ptr(打破shared_ptr循環引用)